forked from xiongyuxing/tiku-backend.net
590 lines
26 KiB
C#
590 lines
26 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Metadata;
|
|
using Tiku.Domain.Commerce;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Domain.Growth;
|
|
using Tiku.Domain.Learning;
|
|
using Tiku.Domain.QuestionBanks;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.IntegrationTests;
|
|
|
|
public sealed class PersistenceModelTests
|
|
{
|
|
private static readonly DbContextOptions<TikuDbContext> Options =
|
|
new DbContextOptionsBuilder<TikuDbContext>()
|
|
.UseNpgsql("Host=localhost;Database=tiku_model_tests;Username=postgres")
|
|
.Options;
|
|
|
|
[Fact]
|
|
public void Core_model_contains_expected_tables()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
var tableNames = context.Model.GetEntityTypes()
|
|
.Select(entity => entity.GetTableName())
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
|
|
Assert.Equal(90, tableNames.Count);
|
|
Assert.Contains("tenants", tableNames);
|
|
Assert.Contains("tenant_settings", tableNames);
|
|
Assert.Contains("content_entries", tableNames);
|
|
Assert.Contains("content_nodes", tableNames);
|
|
Assert.Contains("question_collections", tableNames);
|
|
Assert.Contains("practice_blueprints", tableNames);
|
|
Assert.Contains("vocabulary_units", tableNames);
|
|
Assert.Contains("vocabulary_words", tableNames);
|
|
Assert.Contains("user_word_progress", tableNames);
|
|
Assert.Contains("user_word_favorites", tableNames);
|
|
Assert.Contains("handbook_subjects", tableNames);
|
|
Assert.Contains("handbook_chapters", tableNames);
|
|
Assert.Contains("handbook_entries", tableNames);
|
|
Assert.Contains("question_type_groups", tableNames);
|
|
Assert.Contains("subject_shares", tableNames);
|
|
Assert.Contains("content_assets", tableNames);
|
|
Assert.Contains("content_import_jobs", tableNames);
|
|
Assert.Contains("content_import_items", tableNames);
|
|
Assert.Contains("content_import_issues", tableNames);
|
|
Assert.Contains("images", tableNames);
|
|
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("tenant_auth_providers", tableNames);
|
|
Assert.Contains("sms_verification_codes", tableNames);
|
|
Assert.Contains("auth_login_events", tableNames);
|
|
Assert.Contains("auth_sessions", tableNames);
|
|
Assert.Contains("sms_send_rate_limits", tableNames);
|
|
Assert.Contains("tenant_role_templates", tableNames);
|
|
Assert.Contains("tenant_classes", tableNames);
|
|
Assert.Contains("tenant_class_members", tableNames);
|
|
Assert.Contains("tenant_student_notes", tableNames);
|
|
Assert.Contains("tenant_student_followups", tableNames);
|
|
Assert.Contains("products", tableNames);
|
|
Assert.Contains("svip_plans", tableNames);
|
|
Assert.Contains("orders", tableNames);
|
|
Assert.Contains("order_items", tableNames);
|
|
Assert.Contains("payments", tableNames);
|
|
Assert.Contains("payment_events", tableNames);
|
|
Assert.Contains("entitlements", tableNames);
|
|
Assert.Contains("code_batches", tableNames);
|
|
Assert.Contains("activation_codes", tableNames);
|
|
Assert.Contains("coupons", tableNames);
|
|
Assert.Contains("coupon_redemptions", tableNames);
|
|
Assert.Contains("tenant_payment_accounts", tableNames);
|
|
Assert.Contains("tenant_subscriptions", tableNames);
|
|
Assert.Contains("tenant_usage_records", tableNames);
|
|
Assert.Contains("referral_tracks", tableNames);
|
|
Assert.Contains("referral_codes", tableNames);
|
|
Assert.Contains("referral_leads", tableNames);
|
|
Assert.Contains("referral_team_edges", tableNames);
|
|
Assert.Contains("referral_qrcodes", tableNames);
|
|
Assert.Contains("crm_config", tableNames);
|
|
Assert.Contains("crm_webhook_queue", tableNames);
|
|
Assert.Contains("crm_webhook_log", tableNames);
|
|
Assert.Contains("tenant_commission_settings", tableNames);
|
|
Assert.Contains("commission_settlements", tableNames);
|
|
Assert.Contains("commission_settlement_items", tableNames);
|
|
Assert.Contains("questions", tableNames);
|
|
Assert.Contains("question_versions", tableNames);
|
|
Assert.Contains("answer_records", tableNames);
|
|
}
|
|
|
|
[Fact]
|
|
public void Content_node_path_is_mapped_to_ltree()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
var path = context.Model.FindEntityType(typeof(ContentNode))!
|
|
.FindProperty(nameof(ContentNode.Path))!;
|
|
|
|
Assert.Equal("ltree", path.GetColumnType());
|
|
}
|
|
|
|
[Fact]
|
|
public void Collection_relations_include_tenant_in_foreign_keys()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
var foreignKeys = context.Model.FindEntityType(typeof(QuestionCollection))!
|
|
.GetForeignKeys()
|
|
.Where(foreignKey => foreignKey.Properties.Count > 1)
|
|
.Select(foreignKey => foreignKey.Properties
|
|
.Select(property => property.Name)
|
|
.ToArray());
|
|
|
|
Assert.All(
|
|
foreignKeys,
|
|
properties => Assert.Contains("TenantId", properties));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(nameof(Question.Tags), "'[]'::jsonb")]
|
|
public void Question_json_properties_are_mapped_to_jsonb(
|
|
string propertyName,
|
|
string expectedDefaultValueSql)
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
var property = context.Model.FindEntityType(typeof(Question))!
|
|
.FindProperty(propertyName)!;
|
|
|
|
Assert.Equal("jsonb", property.GetColumnType());
|
|
Assert.Equal(expectedDefaultValueSql, property.GetDefaultValueSql());
|
|
Assert.Equal(typeof(System.Text.Json.JsonElement), property.ClrType);
|
|
}
|
|
|
|
[Fact]
|
|
public void Question_relations_include_tenant_in_foreign_keys()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
var foreignKeys = context.Model.FindEntityType(typeof(Question))!
|
|
.GetForeignKeys()
|
|
.Select(foreignKey => foreignKey.Properties
|
|
.Select(property => property.Name)
|
|
.ToArray())
|
|
.ToArray();
|
|
|
|
Assert.All(
|
|
foreignKeys.Where(properties => properties.Length > 1),
|
|
properties => Assert.Contains("TenantId", properties));
|
|
|
|
Assert.Contains(
|
|
foreignKeys,
|
|
properties => properties.SequenceEqual(
|
|
["TenantId", "Id", "CurrentVersionId"]));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(typeof(VocabularyWord), nameof(VocabularyWord.Tags), "'[]'::jsonb")]
|
|
[InlineData(typeof(VocabularyWord), nameof(VocabularyWord.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(HandbookSubject), nameof(HandbookSubject.MajorLegacyIds), "'[]'::jsonb")]
|
|
[InlineData(typeof(HandbookEntry), nameof(HandbookEntry.Tags), "'[]'::jsonb")]
|
|
[InlineData(typeof(QuestionTypeGroup), nameof(QuestionTypeGroup.Types), "'[]'::jsonb")]
|
|
[InlineData(typeof(UserWordProgress), nameof(UserWordProgress.Metadata), "'{}'::jsonb")]
|
|
public void Study_content_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 User_word_progress_has_review_constraints_and_precision()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
var entityType = context.GetService<IDesignTimeModel>()
|
|
.Model
|
|
.FindEntityType(typeof(UserWordProgress))!;
|
|
var checkConstraints = entityType.GetCheckConstraints()
|
|
.Select(constraint => constraint.Name)
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
|
|
var easeFactor = entityType.FindProperty(nameof(UserWordProgress.EaseFactor))!;
|
|
Assert.Equal(4, easeFactor.GetPrecision());
|
|
Assert.Equal(2, easeFactor.GetScale());
|
|
Assert.Contains("ck_user_word_progress_review_count", checkConstraints);
|
|
Assert.Contains("ck_user_word_progress_correct_streak", checkConstraints);
|
|
Assert.Contains("ck_user_word_progress_ease_factor", checkConstraints);
|
|
}
|
|
|
|
[Fact]
|
|
public void Study_content_relations_include_tenant_in_foreign_keys()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
var entityTypes = new[]
|
|
{
|
|
typeof(VocabularyUnit),
|
|
typeof(VocabularyWord),
|
|
typeof(UserWordProgress),
|
|
typeof(UserWordFavorite),
|
|
typeof(HandbookSubject),
|
|
typeof(HandbookChapter),
|
|
typeof(HandbookEntry),
|
|
typeof(QuestionTypeGroup),
|
|
typeof(SubjectShare)
|
|
};
|
|
|
|
var compositeForeignKeys = entityTypes
|
|
.SelectMany(entityType => context.Model.FindEntityType(entityType)!
|
|
.GetForeignKeys())
|
|
.Where(foreignKey => foreignKey.Properties.Count > 1)
|
|
.Select(foreignKey => foreignKey.Properties
|
|
.Select(property => property.Name)
|
|
.ToArray());
|
|
|
|
Assert.All(
|
|
compositeForeignKeys,
|
|
properties => Assert.Contains("TenantId", properties));
|
|
}
|
|
|
|
[Fact]
|
|
public void Word_user_join_tables_have_tenant_scoped_unique_indexes()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
AssertHasUniqueIndex<UserWordProgress>(
|
|
nameof(UserWordProgress.TenantId),
|
|
nameof(UserWordProgress.UserId),
|
|
nameof(UserWordProgress.WordId));
|
|
AssertHasUniqueIndex<UserWordFavorite>(
|
|
nameof(UserWordFavorite.TenantId),
|
|
nameof(UserWordFavorite.UserId),
|
|
nameof(UserWordFavorite.WordId));
|
|
|
|
void AssertHasUniqueIndex<TEntity>(params string[] propertyNames)
|
|
{
|
|
var indexes = context.Model.FindEntityType(typeof(TEntity))!.GetIndexes();
|
|
|
|
Assert.Contains(
|
|
indexes,
|
|
index => index.IsUnique
|
|
&& index.Properties
|
|
.Select(property => property.Name)
|
|
.SequenceEqual(propertyNames));
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(typeof(ContentAsset), nameof(ContentAsset.AccessRules), "'{}'::jsonb")]
|
|
[InlineData(typeof(ContentAsset), nameof(ContentAsset.VerificationDetails), "'{}'::jsonb")]
|
|
[InlineData(typeof(ContentAsset), nameof(ContentAsset.SecurityFlags), "'{}'::jsonb")]
|
|
[InlineData(typeof(ContentImportJob), nameof(ContentImportJob.RawPayload), "'[]'::jsonb")]
|
|
[InlineData(typeof(ContentImportItem), nameof(ContentImportItem.SourcePayload), "'{}'::jsonb")]
|
|
[InlineData(typeof(ContentImportIssue), nameof(ContentImportIssue.Details), "'{}'::jsonb")]
|
|
[InlineData(typeof(VideoExplanation), nameof(VideoExplanation.KnowledgeTags), "'[]'::jsonb")]
|
|
[InlineData(typeof(QuestionVideo), nameof(QuestionVideo.Metadata), "'{}'::jsonb")]
|
|
public void Asset_import_and_video_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 Import_items_and_question_videos_have_expected_unique_indexes()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
AssertHasUniqueIndex<ContentImportItem>(
|
|
nameof(ContentImportItem.JobId),
|
|
nameof(ContentImportItem.RowNo));
|
|
AssertHasUniqueIndex<QuestionVideo>(
|
|
nameof(QuestionVideo.TenantId),
|
|
nameof(QuestionVideo.QuestionId),
|
|
nameof(QuestionVideo.VideoId),
|
|
nameof(QuestionVideo.VideoType));
|
|
|
|
void AssertHasUniqueIndex<TEntity>(params string[] propertyNames)
|
|
{
|
|
var indexes = context.Model.FindEntityType(typeof(TEntity))!.GetIndexes();
|
|
|
|
Assert.Contains(
|
|
indexes,
|
|
index => index.IsUnique
|
|
&& index.Properties
|
|
.Select(property => property.Name)
|
|
.SequenceEqual(propertyNames));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Asset_import_and_video_relations_include_tenant_in_foreign_keys()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
var entityTypes = new[]
|
|
{
|
|
typeof(ContentAsset),
|
|
typeof(ContentImportJob),
|
|
typeof(ContentImportItem),
|
|
typeof(ContentImportIssue),
|
|
typeof(VideoExplanation),
|
|
typeof(QuestionVideo)
|
|
};
|
|
|
|
var compositeForeignKeys = entityTypes
|
|
.SelectMany(entityType => context.Model.FindEntityType(entityType)!
|
|
.GetForeignKeys())
|
|
.Where(foreignKey => foreignKey.Properties.Count > 1)
|
|
.Select(foreignKey => foreignKey.Properties
|
|
.Select(property => property.Name)
|
|
.ToArray());
|
|
|
|
Assert.All(
|
|
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());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(typeof(TenantAuthProvider), nameof(TenantAuthProvider.ConfigPublic), "'{}'::jsonb")]
|
|
[InlineData(typeof(SmsVerificationCode), nameof(SmsVerificationCode.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(AuthLoginEvent), nameof(AuthLoginEvent.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(AuthSession), nameof(AuthSession.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantRoleTemplate), nameof(TenantRoleTemplate.Permissions), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantRoleTemplate), nameof(TenantRoleTemplate.DataScope), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantClass), nameof(TenantClass.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantClassMember), nameof(TenantClassMember.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantStudentNote), nameof(TenantStudentNote.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantStudentFollowup), nameof(TenantStudentFollowup.Metadata), "'{}'::jsonb")]
|
|
public void Auth_and_tenant_operations_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 Auth_and_class_tables_have_expected_unique_indexes()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
AssertHasUniqueIndex<TenantAuthProvider>(
|
|
nameof(TenantAuthProvider.TenantId),
|
|
nameof(TenantAuthProvider.Provider));
|
|
AssertHasUniqueIndex<TenantClassMember>(
|
|
nameof(TenantClassMember.TenantId),
|
|
nameof(TenantClassMember.ClassId),
|
|
nameof(TenantClassMember.UserId),
|
|
nameof(TenantClassMember.MemberType));
|
|
|
|
void AssertHasUniqueIndex<TEntity>(params string[] propertyNames)
|
|
{
|
|
var indexes = context.Model.FindEntityType(typeof(TEntity))!.GetIndexes();
|
|
|
|
Assert.Contains(
|
|
indexes,
|
|
index => index.IsUnique
|
|
&& index.Properties
|
|
.Select(property => property.Name)
|
|
.SequenceEqual(propertyNames));
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(typeof(Product), nameof(Product.Tags), "'[]'::jsonb")]
|
|
[InlineData(typeof(Product), nameof(Product.DetailImages), "'[]'::jsonb")]
|
|
[InlineData(typeof(Order), nameof(Order.RawPayload), "'{}'::jsonb")]
|
|
[InlineData(typeof(OrderItem), nameof(OrderItem.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(Payment), nameof(Payment.RawPayload), "'{}'::jsonb")]
|
|
[InlineData(typeof(PaymentEvent), nameof(PaymentEvent.Payload), "'{}'::jsonb")]
|
|
[InlineData(typeof(Entitlement), nameof(Entitlement.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantPaymentAccount), nameof(TenantPaymentAccount.ConfigPublic), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantSubscription), nameof(TenantSubscription.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantUsageRecord), nameof(TenantUsageRecord.Metadata), "'{}'::jsonb")]
|
|
public void Commerce_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 Commerce_tables_have_expected_unique_indexes_and_money_types()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
AssertHasUniqueIndex<Order>(nameof(Order.TenantId), nameof(Order.OrderNo));
|
|
AssertHasUniqueIndex<ActivationCode>(nameof(ActivationCode.TenantId), nameof(ActivationCode.Code));
|
|
AssertHasUniqueIndex<TenantPaymentAccount>(
|
|
nameof(TenantPaymentAccount.TenantId),
|
|
nameof(TenantPaymentAccount.Provider));
|
|
|
|
var amount = context.Model.FindEntityType(typeof(Order))!
|
|
.FindProperty(nameof(Order.AmountCents))!;
|
|
Assert.Equal(typeof(int), amount.ClrType);
|
|
|
|
void AssertHasUniqueIndex<TEntity>(params string[] propertyNames)
|
|
{
|
|
var indexes = context.Model.FindEntityType(typeof(TEntity))!.GetIndexes();
|
|
|
|
Assert.Contains(
|
|
indexes,
|
|
index => index.IsUnique
|
|
&& index.Properties
|
|
.Select(property => property.Name)
|
|
.SequenceEqual(propertyNames));
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(typeof(ReferralTrack), nameof(ReferralTrack.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(ReferralCode), nameof(ReferralCode.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(ReferralLead), nameof(ReferralLead.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(ReferralTeamEdge), nameof(ReferralTeamEdge.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(ReferralQrcode), nameof(ReferralQrcode.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(CrmConfig), nameof(CrmConfig.AssignmentPool), "'[]'::jsonb")]
|
|
[InlineData(typeof(CrmConfig), nameof(CrmConfig.AssignmentConfig), "'{}'::jsonb")]
|
|
[InlineData(typeof(CrmWebhookQueueItem), nameof(CrmWebhookQueueItem.Payload), "'{}'::jsonb")]
|
|
[InlineData(typeof(CrmWebhookLog), nameof(CrmWebhookLog.RequestPayload), "'{}'::jsonb")]
|
|
[InlineData(typeof(TenantCommissionSetting), nameof(TenantCommissionSetting.Config), "'{}'::jsonb")]
|
|
[InlineData(typeof(CommissionSettlement), nameof(CommissionSettlement.Metadata), "'{}'::jsonb")]
|
|
[InlineData(typeof(CommissionSettlementItem), nameof(CommissionSettlementItem.Metadata), "'{}'::jsonb")]
|
|
public void Growth_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 Growth_tables_have_expected_unique_indexes_and_decimal_precision()
|
|
{
|
|
using var context = new TikuDbContext(Options);
|
|
|
|
AssertHasUniqueIndex<ReferralCode>(nameof(ReferralCode.TenantId), nameof(ReferralCode.Code));
|
|
AssertHasUniqueIndex<ReferralLead>(nameof(ReferralLead.TenantId), nameof(ReferralLead.StudentUserId));
|
|
AssertHasUniqueIndex<CrmConfig>(nameof(CrmConfig.TenantId));
|
|
AssertHasUniqueIndex<CrmWebhookQueueItem>(nameof(CrmWebhookQueueItem.TenantId), nameof(CrmWebhookQueueItem.IdempotencyKey));
|
|
AssertHasUniqueIndex<CommissionSettlement>(
|
|
nameof(CommissionSettlement.TenantId),
|
|
nameof(CommissionSettlement.SettlementNo));
|
|
AssertHasUniqueIndex<CommissionSettlementItem>(
|
|
nameof(CommissionSettlementItem.TenantId),
|
|
nameof(CommissionSettlementItem.SourceType),
|
|
nameof(CommissionSettlementItem.SourceId));
|
|
|
|
AssertPrecision<TenantCommissionSetting>(nameof(TenantCommissionSetting.DefaultRate), 6, 4);
|
|
AssertPrecision<CommissionSettlement>(nameof(CommissionSettlement.DefaultRate), 6, 4);
|
|
AssertPrecision<CommissionSettlementItem>(nameof(CommissionSettlementItem.CommissionRate), 6, 4);
|
|
|
|
void AssertHasUniqueIndex<TEntity>(params string[] propertyNames)
|
|
{
|
|
var indexes = context.Model.FindEntityType(typeof(TEntity))!.GetIndexes();
|
|
|
|
Assert.Contains(
|
|
indexes,
|
|
index => index.IsUnique
|
|
&& index.Properties
|
|
.Select(property => property.Name)
|
|
.SequenceEqual(propertyNames));
|
|
}
|
|
|
|
void AssertPrecision<TEntity>(string propertyName, int precision, int scale)
|
|
{
|
|
var property = context.Model.FindEntityType(typeof(TEntity))!
|
|
.FindProperty(propertyName)!;
|
|
|
|
Assert.Equal(precision, property.GetPrecision());
|
|
Assert.Equal(scale, property.GetScale());
|
|
}
|
|
}
|
|
}
|