Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
332 lines
11 KiB
Dart
332 lines
11 KiB
Dart
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 тесты для проверки базовой функциональности после деплоя
|
||
///
|
||
/// Эти тесты проверяют:
|
||
/// - Создание БД и таблиц
|
||
/// - Основные CRUD операции
|
||
/// - Интеграцию WordStatisticsManager с TestManager
|
||
/// - Расчет статистики через StatisticsCalculator
|
||
///
|
||
/// Требования:
|
||
/// - 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';
|
||
final port =
|
||
int.tryParse(Platform.environment['TEST_DB_PORT'] ?? '5432') ?? 5432;
|
||
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(
|
||
externalUserId:
|
||
'smoke_test_user_${DateTime.now().millisecondsSinceEpoch}',
|
||
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 карточки с ответами
|
||
expect(
|
||
packProgress.averageAccuracy,
|
||
closeTo(0.5, 0.01),
|
||
); // 1 правильный, 1 неправильный
|
||
});
|
||
|
||
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,
|
||
),
|
||
);
|
||
|
||
final studyDates = await statisticsCalculator.calculateStudyDates(
|
||
testUserId,
|
||
);
|
||
|
||
expect(studyDates, isNotEmpty);
|
||
// Проверить что дата сегодняшнего дня присутствует
|
||
final today = DateTime(now.year, now.month, now.day);
|
||
expect(
|
||
studyDates.any(
|
||
(d) =>
|
||
d.year == today.year &&
|
||
d.month == today.month &&
|
||
d.day == today.day,
|
||
),
|
||
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))),
|
||
endTime: Value(
|
||
PgDateTime(now.add(const Duration(hours: 1, minutes: 20))),
|
||
),
|
||
durationMinutes: 20,
|
||
),
|
||
);
|
||
|
||
final categoryMinutes = await statisticsCalculator
|
||
.calculateCategoryMinutes(testUserId);
|
||
|
||
expect(categoryMinutes, isNotEmpty);
|
||
// Должно быть минимум 35 минут (15 + 20)
|
||
final totalMinutes = categoryMinutes.values.fold<int>(
|
||
0,
|
||
(sum, minutes) => sum + minutes,
|
||
);
|
||
expect(totalMinutes, greaterThanOrEqualTo(35));
|
||
});
|
||
|
||
test('soft delete работает корректно', () async {
|
||
// Создать запись статистики
|
||
final stats = await db.wordStatisticsDao.create(
|
||
userId: testUserId,
|
||
cardId: testCardId,
|
||
correctAnswers: 5,
|
||
incorrectAnswers: 2,
|
||
);
|
||
|
||
// Soft delete
|
||
await (db.update(
|
||
db.wordStatistics,
|
||
)..where((w) => w.id.equals(stats.id))).write(
|
||
WordStatisticsCompanion(
|
||
isDeleted: const Value(true),
|
||
deletedAt: Value(PgDateTime(DateTime.now())),
|
||
),
|
||
);
|
||
|
||
// Проверить что запись не возвращается через 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]),
|
||
);
|
||
});
|
||
});
|
||
}
|