feat: add learning reports and practice access persistence

This commit is contained in:
xiong
2026-07-26 05:24:55 +08:00
parent c217db4597
commit b2282cde8c
11 changed files with 9606 additions and 2 deletions

View File

@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Tiku.Domain.Content;
using Tiku.Domain.Learning;
using Tiku.Domain.QuestionBanks;
using Tiku.Infrastructure.Persistence;
@@ -23,7 +24,7 @@ public sealed class PersistenceModelTests
.Select(entity => entity.GetTableName())
.ToHashSet(StringComparer.Ordinal);
Assert.Equal(45, tableNames.Count);
Assert.Equal(55, tableNames.Count);
Assert.Contains("tenants", tableNames);
Assert.Contains("tenant_settings", tableNames);
Assert.Contains("content_entries", tableNames);
@@ -47,6 +48,16 @@ public sealed class PersistenceModelTests
Assert.Contains("app_assets", tableNames);
Assert.Contains("video_explanations", tableNames);
Assert.Contains("question_videos", tableNames);
Assert.Contains("exam_dates", tableNames);
Assert.Contains("reports", tableNames);
Assert.Contains("report_status_events", tableNames);
Assert.Contains("user_score_events", tableNames);
Assert.Contains("practice_daily_usage", tableNames);
Assert.Contains("practice_access_events", tableNames);
Assert.Contains("practice_session_reports", tableNames);
Assert.Contains("practice_session_report_sections", tableNames);
Assert.Contains("dashboard_daily_stats", tableNames);
Assert.Contains("revenue_daily_stats", tableNames);
Assert.Contains("questions", tableNames);
Assert.Contains("question_versions", tableNames);
Assert.Contains("answer_records", tableNames);
@@ -296,4 +307,67 @@ public sealed class PersistenceModelTests
compositeForeignKeys,
properties => Assert.Contains("TenantId", properties));
}
[Theory]
[InlineData(typeof(ContentNode), nameof(ContentNode.AccessRules), "'{}'::jsonb")]
[InlineData(typeof(QuestionCollection), nameof(QuestionCollection.AccessRules), "'{}'::jsonb")]
[InlineData(typeof(PracticeBlueprint), nameof(PracticeBlueprint.AccessRules), "'{}'::jsonb")]
[InlineData(typeof(PracticeSession), nameof(PracticeSession.AccessSnapshot), "'{}'::jsonb")]
[InlineData(typeof(Report), nameof(Report.Attachments), "'[]'::jsonb")]
[InlineData(typeof(Report), nameof(Report.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PracticeSessionReport), nameof(PracticeSessionReport.QuestionResults), "'[]'::jsonb")]
[InlineData(typeof(PracticeSessionReport), nameof(PracticeSessionReport.WrongQuestionIds), "'[]'::jsonb")]
[InlineData(typeof(PracticeSessionReportSection), nameof(PracticeSessionReportSection.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PracticeDailyUsage), nameof(PracticeDailyUsage.Metadata), "'{}'::jsonb")]
[InlineData(typeof(PracticeAccessEvent), nameof(PracticeAccessEvent.Metadata), "'{}'::jsonb")]
public void Learning_report_json_properties_are_mapped_to_jsonb(
Type entityType,
string propertyName,
string expectedDefaultValueSql)
{
using var context = new TikuDbContext(Options);
var property = context.Model.FindEntityType(entityType)!
.FindProperty(propertyName)!;
Assert.Equal("jsonb", property.GetColumnType());
Assert.Equal(expectedDefaultValueSql, property.GetDefaultValueSql());
Assert.Equal(typeof(System.Text.Json.JsonElement), property.ClrType);
}
[Fact]
public void Practice_usage_unique_scope_treats_nulls_as_not_distinct()
{
using var context = new TikuDbContext(Options);
var index = context.GetService<IDesignTimeModel>()
.Model
.FindEntityType(typeof(PracticeDailyUsage))!
.GetIndexes()
.Single(index => index.IsUnique
&& index.Properties.Select(property => property.Name).SequenceEqual([
nameof(PracticeDailyUsage.TenantId),
nameof(PracticeDailyUsage.UserId),
nameof(PracticeDailyUsage.UsageDate),
nameof(PracticeDailyUsage.ScopeType),
nameof(PracticeDailyUsage.ScopeId)
]));
Assert.False(index.GetAreNullsDistinct());
}
[Fact]
public void Practice_report_scores_have_expected_precision()
{
using var context = new TikuDbContext(Options);
var reportType = context.Model.FindEntityType(typeof(PracticeSessionReport))!;
var score = reportType.FindProperty(nameof(PracticeSessionReport.Score))!;
var accuracy = reportType.FindProperty(nameof(PracticeSessionReport.Accuracy))!;
Assert.Equal(10, score.GetPrecision());
Assert.Equal(2, score.GetScale());
Assert.Equal(6, accuracy.GetPrecision());
Assert.Equal(4, accuracy.GetScale());
}
}