mnemo_cards/mnemo_cards_backend/test/smoke/smoke_tests.dart
Dmitry 3d179c95ef
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 Admin Panel / Deploy Admin Panel (push) Waiting to run
Deploy Admin Panel / Admin Panel Verification (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
stuff
2025-12-17 03:41:47 +03:00

319 lines
10 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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]),
);
});
});
}