mnemo_cards/mnemo_cards_backend/test/smoke/smoke_tests.dart

333 lines
11 KiB
Dart
Raw Normal View History

2025-12-17 00:41:47 +00:00
import 'dart:io';
import 'package:test/test.dart';
import 'package:mnemo_cards_backend/database/database.dart';
import 'package:mnemo_cards_backend/database/tables/users.dart';
import 'package:mnemo_cards_backend/database/tables/packs.dart';
import 'package:mnemo_cards_backend/statistics/word_statistics_manager.dart';
import 'package:mnemo_cards_backend/statistics/statistics_calculator.dart';
/// Smoke тесты для проверки базовой функциональности после деплоя
2026-01-08 13:02:47 +00:00
///
2025-12-17 00:41:47 +00:00
/// Эти тесты проверяют:
/// - Создание БД и таблиц
/// - Основные CRUD операции
/// - Интеграцию WordStatisticsManager с TestManager
/// - Расчет статистики через StatisticsCalculator
2026-01-08 13:02:47 +00:00
///
2025-12-17 00:41:47 +00:00
/// Требования:
/// - PostgreSQL должен быть запущен
/// - Тестовая БД: mnemo_cards_test
void main() {
late AppDatabase db;
late WordStatisticsManager wordStatsManager;
late StatisticsCalculator statisticsCalculator;
late String testUserId;
late String testCardId;
late String testPackId;
setUpAll(() async {
// Подключение к тестовой БД
final host = Platform.environment['TEST_DB_HOST'] ?? 'localhost';
2026-01-08 13:02:47 +00:00
final port =
int.tryParse(Platform.environment['TEST_DB_PORT'] ?? '5432') ?? 5432;
2025-12-17 00:41:47 +00:00
final database = Platform.environment['TEST_DB_NAME'] ?? 'mnemo_cards_test';
final username = Platform.environment['TEST_DB_USER'] ?? 'postgres';
final password = Platform.environment['TEST_DB_PASSWORD'] ?? 'postgres';
db = AppDatabase.connect(
host: host,
port: port,
database: database,
username: username,
password: password,
);
// Создать схему БД
await db.migrator.createAll();
// Создать тестовые данные
final user = await db.userDao.createUser(
UsersCompanion.insert(
2026-01-08 13:02:47 +00:00
externalUserId:
'smoke_test_user_${DateTime.now().millisecondsSinceEpoch}',
2025-12-17 00:41:47 +00:00
name: Value('Smoke Test User'),
email: Value('smoke@test.com'),
),
);
testUserId = user.id;
await db.userDao.createUserData(
UserDatasCompanion.insert(userId: testUserId),
);
final pack = await db.packDao.createPack(
CardPacksCompanion.insert(
title: 'Smoke Test Pack',
subtitle: 'Test Subtitle',
size: 10,
),
);
testPackId = pack.id;
final card = await db.packDao.createCard(
GameCardsCompanion.insert(
original: 'test',
translation: 'тест',
image: 'test.png',
),
);
testCardId = card.id;
await db.packDao.linkCardToPack(testCardId, testPackId);
await db.packDao.linkUserToPack(testUserId, testPackId);
wordStatsManager = WordStatisticsManager(db);
statisticsCalculator = StatisticsCalculator(db);
});
tearDownAll(() async {
// Очистить тестовые данные
await db.delete(db.wordStatistics).go();
await db.delete(db.cardPackCards).go();
await db.delete(db.userPacks).go();
await db.delete(db.gameCards).go();
await db.delete(db.cardPacks).go();
await db.delete(db.userDatas).go();
await db.delete(db.users).go();
await db.close();
});
group('Smoke Tests', () {
test('БД создается без ошибок', () {
expect(db, isNotNull);
expect(db.userDao, isNotNull);
expect(db.packDao, isNotNull);
expect(db.wordStatisticsDao, isNotNull);
expect(db.statisticsDao, isNotNull);
});
test('WordStatistics записываются после ответа', () async {
// Имитация ответа на карточку (как в TestManager.submitTest)
await wordStatsManager.recordAnswer(
userId: testUserId,
cardId: testCardId,
isCorrect: true,
);
final stats = await db.wordStatisticsDao.getByUserAndCard(
testUserId,
testCardId,
);
expect(stats, isNotNull);
expect(stats!.correctAnswers, equals(1));
expect(stats.incorrectAnswers, equals(0));
expect(stats.mastery, equals(1.0));
});
test('packProgress рассчитывается корректно', () async {
// Записать несколько ответов
await wordStatsManager.recordAnswer(
userId: testUserId,
cardId: testCardId,
isCorrect: true,
);
// Создать еще одну карточку в паке
final card2 = await db.packDao.createCard(
GameCardsCompanion.insert(
original: 'test2',
translation: 'тест2',
image: 'test2.png',
),
);
await db.packDao.linkCardToPack(card2.id, testPackId);
await wordStatsManager.recordAnswer(
userId: testUserId,
cardId: card2.id,
isCorrect: false,
);
// Рассчитать packProgress
final packProgress = await statisticsCalculator.calculatePackProgress(
testUserId,
testPackId,
);
expect(packProgress.packId, equals(testPackId));
expect(packProgress.totalCards, equals(10));
expect(packProgress.learnedCards, equals(2)); // 2 карточки с ответами
2026-01-08 13:02:47 +00:00
expect(
packProgress.averageAccuracy,
closeTo(0.5, 0.01),
); // 1 правильный, 1 неправильный
2025-12-17 00:41:47 +00:00
});
test('studyDates рассчитываются из StudySessions', () async {
// Создать сессию изучения
final now = DateTime.now();
await db.statisticsDao.createSession(
StudySessionsCompanion.insert(
userId: testUserId,
packId: testPackId,
startTime: PgDateTime(now),
endTime: Value(PgDateTime(now.add(const Duration(minutes: 10)))),
durationMinutes: 10,
),
);
2026-01-08 13:02:47 +00:00
final studyDates = await statisticsCalculator.calculateStudyDates(
testUserId,
);
2025-12-17 00:41:47 +00:00
expect(studyDates, isNotEmpty);
// Проверить что дата сегодняшнего дня присутствует
final today = DateTime(now.year, now.month, now.day);
expect(
2026-01-08 13:02:47 +00:00
studyDates.any(
(d) =>
d.year == today.year &&
d.month == today.month &&
d.day == today.day,
),
2025-12-17 00:41:47 +00:00
isTrue,
);
});
test('categoryMinutes рассчитываются из StudySessions', () async {
// Создать несколько сессий
final now = DateTime.now();
await db.statisticsDao.createSession(
StudySessionsCompanion.insert(
userId: testUserId,
packId: testPackId,
startTime: PgDateTime(now),
endTime: Value(PgDateTime(now.add(const Duration(minutes: 15)))),
durationMinutes: 15,
),
);
await db.statisticsDao.createSession(
StudySessionsCompanion.insert(
userId: testUserId,
packId: testPackId,
startTime: PgDateTime(now.add(const Duration(hours: 1))),
2026-01-08 13:02:47 +00:00
endTime: Value(
PgDateTime(now.add(const Duration(hours: 1, minutes: 20))),
),
2025-12-17 00:41:47 +00:00
durationMinutes: 20,
),
);
2026-01-08 13:02:47 +00:00
final categoryMinutes = await statisticsCalculator
.calculateCategoryMinutes(testUserId);
2025-12-17 00:41:47 +00:00
expect(categoryMinutes, isNotEmpty);
// Должно быть минимум 35 минут (15 + 20)
2026-01-08 13:02:47 +00:00
final totalMinutes = categoryMinutes.values.fold<int>(
0,
(sum, minutes) => sum + minutes,
);
2025-12-17 00:41:47 +00:00
expect(totalMinutes, greaterThanOrEqualTo(35));
});
test('soft delete работает корректно', () async {
// Создать запись статистики
final stats = await db.wordStatisticsDao.create(
userId: testUserId,
cardId: testCardId,
correctAnswers: 5,
incorrectAnswers: 2,
);
// Soft delete
2026-01-08 13:02:47 +00:00
await (db.update(
db.wordStatistics,
)..where((w) => w.id.equals(stats.id))).write(
WordStatisticsCompanion(
isDeleted: const Value(true),
deletedAt: Value(PgDateTime(DateTime.now())),
),
);
2025-12-17 00:41:47 +00:00
// Проверить что запись не возвращается через getActiveById
final activeRecord = await db.wordStatisticsDao.getActiveById(stats.id);
expect(activeRecord, isNull);
// Проверить что запись физически существует в БД
final allRecords = await db.select(db.wordStatistics).get();
expect(allRecords.length, greaterThan(0));
expect(allRecords.any((r) => r.id == stats.id && r.isDeleted), isTrue);
});
test('WordStatisticsManager обновляет существующую статистику', () async {
// Первый ответ
await wordStatsManager.recordAnswer(
userId: testUserId,
cardId: testCardId,
isCorrect: true,
);
// Второй ответ
await wordStatsManager.recordAnswer(
userId: testUserId,
cardId: testCardId,
isCorrect: true,
);
// Третий ответ (неправильный)
await wordStatsManager.recordAnswer(
userId: testUserId,
cardId: testCardId,
isCorrect: false,
);
final stats = await db.wordStatisticsDao.getByUserAndCard(
testUserId,
testCardId,
);
expect(stats, isNotNull);
expect(stats!.correctAnswers, equals(2));
expect(stats.incorrectAnswers, equals(1));
expect(stats.mastery, closeTo(2.0 / 3.0, 0.001));
});
test('getPackStatistics возвращает статистику по карточкам пака', () async {
// Создать еще одну карточку в паке
final card2 = await db.packDao.createCard(
GameCardsCompanion.insert(
original: 'test2',
translation: 'тест2',
image: 'test2.png',
),
);
await db.packDao.linkCardToPack(card2.id, testPackId);
// Записать ответы для обеих карточек
await wordStatsManager.recordAnswer(
userId: testUserId,
cardId: testCardId,
isCorrect: true,
);
await wordStatsManager.recordAnswer(
userId: testUserId,
cardId: card2.id,
isCorrect: false,
);
final packStats = await wordStatsManager.getPackStatistics(
testUserId,
testPackId,
);
expect(packStats.length, equals(2));
expect(
packStats.map((s) => s.cardId).toSet(),
containsAll([testCardId, card2.id]),
);
});
});
}