350 lines
11 KiB
C#
350 lines
11 KiB
C#
using ChatBot.Data;
|
|
using ChatBot.Migrations;
|
|
using FluentAssertions;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace ChatBot.Tests.Data;
|
|
|
|
public class MigrationsTests : IDisposable
|
|
{
|
|
private readonly ServiceProvider _serviceProvider;
|
|
private readonly ChatBotDbContext _dbContext;
|
|
private bool _disposed;
|
|
|
|
public MigrationsTests()
|
|
{
|
|
var services = new ServiceCollection();
|
|
|
|
// Add in-memory database with unique name per test
|
|
services.AddDbContext<ChatBotDbContext>(options =>
|
|
options.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
);
|
|
|
|
_serviceProvider = services.BuildServiceProvider();
|
|
_dbContext = _serviceProvider.GetRequiredService<ChatBotDbContext>();
|
|
}
|
|
|
|
[Fact]
|
|
public void InitialCreateMigration_ShouldHaveCorrectName()
|
|
{
|
|
// Arrange
|
|
var migration = new InitialCreate();
|
|
|
|
// Assert
|
|
migration.Should().NotBeNull();
|
|
migration.GetType().Name.Should().Be("InitialCreate");
|
|
}
|
|
|
|
[Fact]
|
|
public void InitialCreateMigration_ShouldInheritFromMigration()
|
|
{
|
|
// Arrange
|
|
var migration = new InitialCreate();
|
|
|
|
// Assert
|
|
migration.Should().BeAssignableTo<Microsoft.EntityFrameworkCore.Migrations.Migration>();
|
|
}
|
|
|
|
[Fact]
|
|
public void InitialCreateMigration_ShouldBeInstantiable()
|
|
{
|
|
// Arrange & Act
|
|
var migration = new InitialCreate();
|
|
|
|
// Assert
|
|
migration.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void InitialCreateMigration_ShouldHaveCorrectConstants()
|
|
{
|
|
// Arrange & Act & Assert
|
|
// Use reflection to access private constants
|
|
var migrationType = typeof(InitialCreate);
|
|
|
|
var chatSessionsTableNameField = migrationType.GetField(
|
|
"ChatSessionsTableName",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
|
);
|
|
chatSessionsTableNameField.Should().NotBeNull();
|
|
chatSessionsTableNameField!.GetValue(null).Should().Be("chat_sessions");
|
|
|
|
var chatMessagesTableNameField = migrationType.GetField(
|
|
"ChatMessagesTableName",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
|
);
|
|
chatMessagesTableNameField.Should().NotBeNull();
|
|
chatMessagesTableNameField!.GetValue(null).Should().Be("chat_messages");
|
|
|
|
var chatSessionsIdColumnField = migrationType.GetField(
|
|
"ChatSessionsIdColumn",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
|
);
|
|
chatSessionsIdColumnField.Should().NotBeNull();
|
|
chatSessionsIdColumnField!.GetValue(null).Should().Be("id");
|
|
|
|
var chatMessagesSessionIdColumnField = migrationType.GetField(
|
|
"ChatMessagesSessionIdColumn",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
|
);
|
|
chatMessagesSessionIdColumnField.Should().NotBeNull();
|
|
chatMessagesSessionIdColumnField!.GetValue(null).Should().Be("session_id");
|
|
|
|
var integerTypeField = migrationType.GetField(
|
|
"IntegerType",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
|
|
);
|
|
integerTypeField.Should().NotBeNull();
|
|
integerTypeField!.GetValue(null).Should().Be("integer");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InitialCreateMigration_ShouldApplySuccessfullyToDatabase()
|
|
{
|
|
// Arrange
|
|
var options = new DbContextOptionsBuilder<ChatBotDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
using var context = new ChatBotDbContext(options);
|
|
|
|
// Act
|
|
await context.Database.EnsureCreatedAsync();
|
|
|
|
// Assert
|
|
var canConnect = await context.Database.CanConnectAsync();
|
|
canConnect.Should().BeTrue();
|
|
|
|
// Verify tables exist by trying to query them
|
|
var sessions = await context.ChatSessions.ToListAsync();
|
|
var messages = await context.ChatMessages.ToListAsync();
|
|
|
|
sessions.Should().NotBeNull();
|
|
messages.Should().NotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InitialCreateMigration_ShouldCreateCorrectTableStructure()
|
|
{
|
|
// Arrange
|
|
var options = new DbContextOptionsBuilder<ChatBotDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
using var context = new ChatBotDbContext(options);
|
|
|
|
// Act
|
|
await context.Database.EnsureCreatedAsync();
|
|
|
|
// Assert
|
|
var model = context.Model;
|
|
|
|
// Check chat_sessions entity
|
|
var chatSessionEntity = model.FindEntityType(
|
|
typeof(ChatBot.Models.Entities.ChatSessionEntity)
|
|
);
|
|
chatSessionEntity.Should().NotBeNull();
|
|
chatSessionEntity!.GetTableName().Should().Be("chat_sessions");
|
|
|
|
// Check chat_messages entity
|
|
var chatMessageEntity = model.FindEntityType(
|
|
typeof(ChatBot.Models.Entities.ChatMessageEntity)
|
|
);
|
|
chatMessageEntity.Should().NotBeNull();
|
|
chatMessageEntity!.GetTableName().Should().Be("chat_messages");
|
|
|
|
// Check foreign key relationship
|
|
var foreignKeys = chatMessageEntity.GetForeignKeys().ToList();
|
|
foreignKeys.Should().HaveCount(1);
|
|
|
|
var foreignKey = foreignKeys[0];
|
|
foreignKey.PrincipalEntityType.Should().Be(chatSessionEntity);
|
|
var properties = foreignKey.Properties.ToList();
|
|
properties.Should().HaveCount(1);
|
|
properties[0].Name.Should().Be("SessionId");
|
|
foreignKey.DeleteBehavior.Should().Be(DeleteBehavior.Cascade);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InitialCreateMigration_ShouldCreateIndexes()
|
|
{
|
|
// Arrange
|
|
var options = new DbContextOptionsBuilder<ChatBotDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
using var context = new ChatBotDbContext(options);
|
|
|
|
// Act
|
|
await context.Database.EnsureCreatedAsync();
|
|
|
|
// Assert
|
|
var model = context.Model;
|
|
|
|
// Check chat_sessions entity indexes
|
|
var chatSessionEntity = model.FindEntityType(
|
|
typeof(ChatBot.Models.Entities.ChatSessionEntity)
|
|
);
|
|
chatSessionEntity.Should().NotBeNull();
|
|
|
|
var sessionIndexes = chatSessionEntity!.GetIndexes().ToList();
|
|
sessionIndexes.Should().NotBeEmpty();
|
|
|
|
// Check chat_messages entity indexes
|
|
var chatMessageEntity = model.FindEntityType(
|
|
typeof(ChatBot.Models.Entities.ChatMessageEntity)
|
|
);
|
|
chatMessageEntity.Should().NotBeNull();
|
|
|
|
var messageIndexes = chatMessageEntity!.GetIndexes().ToList();
|
|
messageIndexes.Should().NotBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InitialCreateMigration_ShouldCreateUniqueConstraintOnSessionId()
|
|
{
|
|
// Arrange
|
|
var options = new DbContextOptionsBuilder<ChatBotDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
using var context = new ChatBotDbContext(options);
|
|
|
|
// Act
|
|
await context.Database.EnsureCreatedAsync();
|
|
|
|
// Assert
|
|
var model = context.Model;
|
|
|
|
// Check chat_sessions entity has unique index on SessionId
|
|
var chatSessionEntity = model.FindEntityType(
|
|
typeof(ChatBot.Models.Entities.ChatSessionEntity)
|
|
);
|
|
chatSessionEntity.Should().NotBeNull();
|
|
|
|
var sessionIdProperty = chatSessionEntity!.FindProperty("SessionId");
|
|
sessionIdProperty.Should().NotBeNull();
|
|
|
|
var uniqueIndexes = chatSessionEntity
|
|
.GetIndexes()
|
|
.Where(i => i.IsUnique && i.Properties.Contains(sessionIdProperty))
|
|
.ToList();
|
|
|
|
uniqueIndexes.Should().NotBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InitialCreateMigration_ShouldCreateCompositeIndexOnSessionIdAndMessageOrder()
|
|
{
|
|
// Arrange
|
|
var options = new DbContextOptionsBuilder<ChatBotDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
using var context = new ChatBotDbContext(options);
|
|
|
|
// Act
|
|
await context.Database.EnsureCreatedAsync();
|
|
|
|
// Assert
|
|
var model = context.Model;
|
|
|
|
// Check chat_messages entity has composite index
|
|
var chatMessageEntity = model.FindEntityType(
|
|
typeof(ChatBot.Models.Entities.ChatMessageEntity)
|
|
);
|
|
chatMessageEntity.Should().NotBeNull();
|
|
|
|
var sessionIdProperty = chatMessageEntity!.FindProperty("SessionId");
|
|
var messageOrderProperty = chatMessageEntity.FindProperty("MessageOrder");
|
|
|
|
sessionIdProperty.Should().NotBeNull();
|
|
messageOrderProperty.Should().NotBeNull();
|
|
|
|
var compositeIndexes = chatMessageEntity
|
|
.GetIndexes()
|
|
.Where(i =>
|
|
i.Properties.Count == 2
|
|
&& i.Properties.Contains(sessionIdProperty)
|
|
&& i.Properties.Contains(messageOrderProperty)
|
|
)
|
|
.ToList();
|
|
|
|
compositeIndexes.Should().NotBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InitialCreateMigration_ShouldSupportCascadeDelete()
|
|
{
|
|
// Arrange
|
|
var options = new DbContextOptionsBuilder<ChatBotDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
using var context = new ChatBotDbContext(options);
|
|
|
|
// Act
|
|
await context.Database.EnsureCreatedAsync();
|
|
|
|
// Create test data
|
|
var session = new ChatBot.Models.Entities.ChatSessionEntity
|
|
{
|
|
SessionId = "test-session",
|
|
ChatId = 12345L,
|
|
ChatType = "private",
|
|
CreatedAt = DateTime.UtcNow,
|
|
LastUpdatedAt = DateTime.UtcNow,
|
|
};
|
|
|
|
context.ChatSessions.Add(session);
|
|
await context.SaveChangesAsync();
|
|
|
|
var message = new ChatBot.Models.Entities.ChatMessageEntity
|
|
{
|
|
SessionId = session.Id,
|
|
Content = "Test message",
|
|
Role = "user",
|
|
CreatedAt = DateTime.UtcNow,
|
|
MessageOrder = 1,
|
|
};
|
|
|
|
context.ChatMessages.Add(message);
|
|
await context.SaveChangesAsync();
|
|
|
|
// Verify data exists
|
|
var messageCount = await context.ChatMessages.CountAsync();
|
|
messageCount.Should().Be(1);
|
|
|
|
// Test cascade delete
|
|
context.ChatSessions.Remove(session);
|
|
await context.SaveChangesAsync();
|
|
|
|
// Assert - message should be deleted due to cascade
|
|
var remainingMessageCount = await context.ChatMessages.CountAsync();
|
|
remainingMessageCount.Should().Be(0);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (disposing)
|
|
{
|
|
_dbContext?.Dispose();
|
|
_serviceProvider?.Dispose();
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|