Files
English/kouyu_english/test/local_store_test.dart
T

166 lines
5.3 KiB
Dart

import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kouyu_english/core/local_store.dart';
import 'dart:convert';
void main() {
test(
'a legacy snapshot migrates into canonical entities without replacement',
() async {
final executor = NativeDatabase.memory();
final store = LocalSnapshotStore.forTesting(executor);
const legacy = '''{
"onboardingComplete": true,
"lessonStep": "reading",
"activeLessonId": "a0-02",
"attemptEvidence": [],
"mastery": [],
"reviews": []
}''';
// Open the schema first, then model the row left by a pre-entity build.
expect(await store.read(), isNull);
await executor.runCustom(
'''INSERT INTO learning_snapshots(snapshot_id, schema_version, payload, updated_at)
VALUES (?, ?, ?, ?)''',
const ['current', 1, legacy, '2026-09-14T00:00:00.000Z'],
);
expect(await store.read(), legacy);
await store.write(legacy);
final restored =
jsonDecode((await store.read())!) as Map<String, dynamic>;
expect(restored['activeLessonId'], 'a0-02');
expect(restored['onboardingComplete'], isTrue);
expect(
await executor.runSelect(
'SELECT payload FROM learning_snapshots WHERE snapshot_id = ?',
const ['current'],
),
[
<String, Object?>{'payload': legacy},
],
);
expect(
await executor.runSelect(
'SELECT profile_id FROM learner_profiles',
const [],
),
[
<String, Object?>{'profile_id': 'current'},
],
);
await executor.close();
},
);
test(
'SQLite entity store restores canonical state without writing a snapshot',
() async {
final executor = NativeDatabase.memory();
final store = LocalSnapshotStore.forTesting(executor);
expect(await store.read(), isNull);
const first = '''{
"revision": 1,
"attemptEvidence": [{"id":"a1","itemId":"A0-P01","taskId":"t1","skill":"speaking","inputMode":"speechToText","outcome":"independentSuccess","createdAt":"2026-09-14T00:00:00.000Z","assisted":false,"variantIndex":0,"originalTranscript":"Hello","transcriptConfirmed":true,"transcriptEdited":false}],
"mastery": [{"id":"A0-P01","label":"Hello","status":"recognize","checkpoint":1,"needsReview":false}],
"reviews": [{"id":"A0-P01","dueAt":"2026-09-15T00:00:00.000Z","attempts":1,"successfulReviews":1,"variantIndex":0}],
"temporaryLexicon": [{"query":"library","definition":"图书馆","provider":"compatible","model":"demo","createdAt":"2026-09-14T00:00:00.000Z"}],
"lessonStep":"speaking", "activeLessonId":"a0-01"
}''';
await store.write(first);
final restored =
jsonDecode((await store.read())!) as Map<String, dynamic>;
expect(restored['lessonStep'], 'speaking');
expect(restored['attemptEvidence'], hasLength(1));
expect(restored['mastery'], hasLength(1));
expect(
await executor.runSelect(
'SELECT snapshot_id FROM learning_snapshots',
const [],
),
isEmpty,
);
expect(
await executor.runSelect(
'SELECT item_id FROM attempt_evidence',
const [],
),
[
<String, Object?>{'item_id': 'A0-P01'},
],
);
await executor.runCustom(
'UPDATE learning_snapshots SET payload = ? WHERE snapshot_id = ?',
const ['{"corrupt":"backup only"}', 'current'],
);
final canonicalAfterSnapshotChange =
jsonDecode((await store.read())!) as Map<String, dynamic>;
expect(canonicalAfterSnapshotChange['mastery'], hasLength(1));
expect(
await executor.runSelect(
'SELECT original_transcript, transcript_confirmed, transcript_edited FROM attempt_evidence',
const [],
),
[
<String, Object?>{
'original_transcript': 'Hello',
'transcript_confirmed': 1,
'transcript_edited': 0,
},
],
);
expect(
await executor.runSelect(
'SELECT definition FROM temporary_lexicon_entries',
const [],
),
[
<String, Object?>{'definition': '图书馆'},
],
);
expect(
await executor.runSelect('SELECT status FROM mastery_items', const []),
[
<String, Object?>{'status': 'recognize'},
],
);
expect(
await executor.runSelect('SELECT item_id FROM review_items', const []),
[
<String, Object?>{'item_id': 'A0-P01'},
],
);
expect(
await executor.runSelect(
'SELECT session_type FROM study_sessions',
const [],
),
[
<String, Object?>{'session_type': 'lesson'},
],
);
await store.write('{"revision":2}');
expect(
(jsonDecode((await store.read())!)
as Map<String, dynamic>)['attemptEvidence'],
isEmpty,
);
await store.clear();
expect(await store.read(), isNull);
expect(
await executor.runSelect(
'SELECT query_key FROM temporary_lexicon_entries',
const [],
),
isEmpty,
);
await executor.close();
},
);
}