@Tags(['integration', 'postgres']) library; import 'dart:convert'; import 'dart:math'; import 'package:cyberhybridhub_server/pipeline/question_pipeline.dart'; import 'package:cyberhybridhub_server/spelling/spelling_item_selector.dart'; import 'package:cyberhybridhub_server/spelling/spelling_items_db.dart'; import 'package:cyberhybridhub_server/spelling/user_spelling_progress_db.dart'; import 'package:postgres/postgres.dart'; import 'package:test/test.dart'; import '../helpers/test_db.dart'; void main() { TestDb? testDb; setUpAll(() async { testDb = await TestDb.open(); }); tearDown(() async { if (testDb != null) { await testDb!.truncateTradingTables(); } }); tearDownAll(() async { await testDb?.close(); }); test('catalog seeds two items per curated word', () async { if (testDb == null) { markTestSkipped('Set DATABASE_URL or TEST_DATABASE_URL for integration tests'); return; } final Result words = await testDb!.connection.execute( 'SELECT COUNT(*)::int FROM spelling_words', ); final Result items = await testDb!.connection.execute( 'SELECT COUNT(*)::int FROM spelling_items', ); expect((words.first[0]! as num).toInt(), 10); expect((items.first[0]! as num).toInt(), 20); }); test('wrong answer increases familiarity weight for next pick', () async { if (testDb == null) { markTestSkipped('Set DATABASE_URL or TEST_DATABASE_URL for integration tests'); return; } const String uid = 'spelling-weight-uid'; await testDb!.seedUser(uid); final SpellingItemsDb itemsDb = testDb!.spellingItemsDb; final UserSpellingProgressDb progressDb = UserSpellingProgressDb(testDb!.connection); final SpellingItemSelector selector = SpellingItemSelector( itemsDb: itemsDb, random: Random(1), ); final picked = await selector.pickForUser(uid); expect(picked, isNotNull); await progressDb.recordQuizOutcome( firebaseUid: uid, spellingItemId: picked!.id, correct: false, ); final int? score = await progressDb.familiarityScore( firebaseUid: uid, spellingItemId: picked.id, ); expect(score, -1); final QuestionPipeline pipeline = testDb!.questionPipeline(); await pipeline.maintainSpellingForUser(uid); final Result reserved = await testDb!.connection.execute( Sql.named( ''' SELECT id FROM questions WHERE assigned_user_id = @uid ORDER BY created_at DESC LIMIT 1 ''', ), parameters: {'uid': uid}, ); await pipeline.prepareSpellingQuestion( firebaseUid: uid, questionId: reserved.first[0]!.toString(), ); final Result rows = await testDb!.connection.execute( Sql.named( ''' SELECT metadata FROM questions WHERE assigned_user_id = @uid ORDER BY created_at DESC LIMIT 1 ''', ), parameters: {'uid': uid}, ); final Object? metadataRaw = rows.first[0]; final Map metadata = metadataRaw is Map ? metadataRaw : Map.from( jsonDecode(metadataRaw.toString()) as Map, ); expect(metadata['spelling_item_id'], isNotEmpty); }); test('quiz answer updates user_spelling_progress', () async { if (testDb == null) { markTestSkipped('Set DATABASE_URL or TEST_DATABASE_URL for integration tests'); return; } const String uid = 'spelling-progress-uid'; await testDb!.seedUser(uid); final QuestionPipeline pipeline = testDb!.questionPipeline(); await pipeline.maintainSpellingForUser(uid); final Result reserved = await testDb!.connection.execute( Sql.named( ''' SELECT id FROM questions WHERE assigned_user_id = @uid ORDER BY created_at LIMIT 1 ''', ), parameters: {'uid': uid}, ); await pipeline.prepareSpellingQuestion( firebaseUid: uid, questionId: reserved.first[0]!.toString(), ); final Result first = await testDb!.connection.execute( Sql.named( ''' SELECT id, correct_answer, metadata FROM questions WHERE assigned_user_id = @uid ORDER BY created_at LIMIT 1 ''', ), parameters: {'uid': uid}, ); final String questionId = first.first[0]!.toString(); final Map metadata = _readMetadata(first.first[2]); final String itemId = metadata['spelling_item_id']! as String; final String canonicalWord = metadata['canonical_word']! as String; final Map? answered = await testDb!.questionsDb.submitAnswer( questionId: questionId, assignedUserId: uid, userResponseText: canonicalWord, ); expect(answered, isNotNull); await pipeline.onAnswerSubmitted( firebaseUid: uid, answeredQuestion: answered!, userResponse: answered['userResponse'] as num? ?? 0, ); final Result progress = await testDb!.connection.execute( Sql.named( ''' SELECT familiarity_score, attempt_count, correct_count, last_outcome FROM user_spelling_progress WHERE firebase_uid = @uid AND spelling_item_id = @item_id::uuid ''', ), parameters: {'uid': uid, 'item_id': itemId}, ); expect(progress, hasLength(1)); expect(progress.first[0], 1); expect(progress.first[1], 1); expect(progress.first[2], 1); expect(progress.first[3], 'correct'); }); } Map _readMetadata(Object? raw) { if (raw is Map) { return raw; } if (raw is Map) { return Map.from(raw); } return Map.from( jsonDecode(raw.toString()) as Map, ); }