test: Repository tests for Drift database layer

This commit is contained in:
TrochtaOndrej
2026-03-18 04:42:40 +01:00
parent 652250270b
commit 3740898bcb
3 changed files with 459 additions and 0 deletions
@@ -0,0 +1,153 @@
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kittie_mobile/data/db/app_database.dart';
import 'package:kittie_mobile/data/repositories/pet_repository.dart';
import 'package:kittie_mobile/data/repositories/user_repository.dart';
import 'package:kittie_mobile/domain/entities/pet_state_entity.dart';
import 'package:kittie_mobile/domain/entities/pet_type.dart';
AppDatabase _openMemory() => AppDatabase(NativeDatabase.memory());
PetStateEntity _makePetState({
String id = 'pet-1',
String userId = 'user-1',
int streakDays = 0,
int totalTasksDone = 0,
int? energyToday,
}) {
return PetStateEntity(
id: id,
userId: userId,
streakDays: streakDays,
totalTasksDone: totalTasksDone,
energyToday: energyToday,
updatedAt: DateTime(2024, 1, 1),
);
}
void main() {
late AppDatabase db;
late PetRepository repo;
setUp(() async {
db = _openMemory();
repo = PetRepository(db);
await UserRepository(db).createUser(
id: 'user-1',
deviceId: 'dev-1',
name: 'Test',
petType: PetType.cat,
petName: 'Yoshi',
);
});
tearDown(() async {
await db.close();
});
group('PetRepository', () {
test('should return null when no pet state exists', () async {
final state = await repo.getPetState('user-1');
expect(state, isNull);
});
test('should return inserted pet state when getPetState is called', () async {
await repo.insertPetState(_makePetState());
final state = await repo.getPetState('user-1');
expect(state, isNotNull);
expect(state!.id, 'pet-1');
expect(state.userId, 'user-1');
expect(state.streakDays, 0);
expect(state.totalTasksDone, 0);
});
test('should persist totalTasksDone and streakDays when updatePetState is called', () async {
await repo.insertPetState(_makePetState());
final state = (await repo.getPetState('user-1'))!;
final updated = state.copyWith(totalTasksDone: 5, streakDays: 3);
await repo.updatePetState(updated);
final fetched = await repo.getPetState('user-1');
expect(fetched!.totalTasksDone, 5);
expect(fetched.streakDays, 3);
});
test('should set energyToday and energyDate when setEnergyToday is called', () async {
await repo.insertPetState(_makePetState());
await repo.setEnergyToday('user-1', 3);
final state = await repo.getPetState('user-1');
expect(state!.energyToday, 3);
expect(state.energyDate, isNotNull);
});
test('should update energyToday value when setEnergyToday is called again', () async {
await repo.insertPetState(_makePetState(energyToday: 1));
await repo.setEnergyToday('user-1', 2);
final state = await repo.getPetState('user-1');
expect(state!.energyToday, 2);
});
test('should increment streakDays by 1 when incrementStreak is called', () async {
await repo.insertPetState(_makePetState(streakDays: 4));
await repo.incrementStreak('user-1');
final state = await repo.getPetState('user-1');
expect(state!.streakDays, 5);
});
test('should accumulate streakDays correctly after multiple incrementStreak calls', () async {
await repo.insertPetState(_makePetState());
await repo.incrementStreak('user-1');
await repo.incrementStreak('user-1');
await repo.incrementStreak('user-1');
final state = await repo.getPetState('user-1');
expect(state!.streakDays, 3);
});
test('should emit null when watchPetState is called and no state exists', () async {
final stream = repo.watchPetState('user-1');
await expectLater(stream, emits(isNull));
});
test('should emit state after insert when watchPetState is used', () async {
final stream = repo.watchPetState('user-1');
await repo.insertPetState(_makePetState(streakDays: 7));
await expectLater(
stream,
emitsThrough(
predicate<PetStateEntity?>(
(s) => s != null && s.streakDays == 7,
),
),
);
});
test('should emit updated state after setEnergyToday when watchPetState is used', () async {
await repo.insertPetState(_makePetState());
final stream = repo.watchPetState('user-1');
await repo.setEnergyToday('user-1', 2);
await expectLater(
stream,
emitsThrough(
predicate<PetStateEntity?>(
(s) => s != null && s.energyToday == 2,
),
),
);
});
});
}
@@ -0,0 +1,198 @@
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kittie_mobile/data/db/app_database.dart';
import 'package:kittie_mobile/data/repositories/task_repository.dart';
import 'package:kittie_mobile/data/repositories/user_repository.dart';
import 'package:kittie_mobile/domain/entities/pet_type.dart';
import 'package:kittie_mobile/domain/entities/task_entity.dart';
import 'package:kittie_mobile/domain/entities/task_status.dart';
AppDatabase _openMemory() => AppDatabase(NativeDatabase.memory());
TaskEntity _makeTask({
String id = 'task-1',
String userId = 'user-1',
String title = 'Test Task',
TaskStatus status = TaskStatus.pending,
int energyLevel = 2,
int sortOrder = 0,
}) {
final now = DateTime(2024, 1, 1);
return TaskEntity(
id: id,
userId: userId,
title: title,
status: status,
energyLevel: energyLevel,
sortOrder: sortOrder,
createdAt: now,
updatedAt: now,
);
}
void main() {
late AppDatabase db;
late TaskRepository repo;
setUp(() async {
db = _openMemory();
repo = TaskRepository(db);
await UserRepository(db).createUser(
id: 'user-1',
deviceId: 'dev-1',
name: 'Test',
petType: PetType.cat,
petName: 'Yoshi',
);
});
tearDown(() async {
await db.close();
});
group('TaskRepository', () {
test('should return empty list when no tasks exist', () async {
final tasks = await repo.getAllTasks('user-1');
expect(tasks, isEmpty);
});
test('should return inserted task when getAllTasks is called', () async {
await repo.insertTask(_makeTask());
final tasks = await repo.getAllTasks('user-1');
expect(tasks.length, 1);
expect(tasks.first.id, 'task-1');
expect(tasks.first.title, 'Test Task');
});
test('should order tasks by sortOrder ascending', () async {
await repo.insertTask(_makeTask(id: 'task-3', sortOrder: 3));
await repo.insertTask(_makeTask(id: 'task-1', sortOrder: 1));
await repo.insertTask(_makeTask(id: 'task-2', sortOrder: 2));
final tasks = await repo.getAllTasks('user-1');
expect(tasks.map((t) => t.id).toList(), ['task-1', 'task-2', 'task-3']);
});
test('should return only pending tasks when getTodayTasks is called', () async {
await repo.insertTask(_makeTask(id: 'p1', status: TaskStatus.pending));
await repo.insertTask(_makeTask(id: 'd1', status: TaskStatus.done));
await repo.insertTask(_makeTask(id: 'p2', status: TaskStatus.pending));
await repo.insertTask(_makeTask(id: 'a1', status: TaskStatus.archived));
final tasks = await repo.getTodayTasks('user-1');
expect(tasks.length, 2);
expect(tasks.every((t) => t.status == TaskStatus.pending), isTrue);
});
test('should return task for known id', () async {
await repo.insertTask(_makeTask());
final task = await repo.getTaskById('task-1');
expect(task, isNotNull);
expect(task!.id, 'task-1');
});
test('should return null for unknown id', () async {
final task = await repo.getTaskById('nonexistent');
expect(task, isNull);
});
test('should persist title and energyLevel when updateTask is called', () async {
await repo.insertTask(_makeTask());
final task = (await repo.getTaskById('task-1'))!;
final updated = task.copyWith(title: 'Updated Title', energyLevel: 3);
await repo.updateTask(updated);
final fetched = await repo.getTaskById('task-1');
expect(fetched!.title, 'Updated Title');
expect(fetched.energyLevel, 3);
});
test('should remove task when deleteTask is called', () async {
await repo.insertTask(_makeTask());
await repo.deleteTask('task-1');
final tasks = await repo.getAllTasks('user-1');
expect(tasks, isEmpty);
});
test('should set status to done when completeTask is called', () async {
await repo.insertTask(_makeTask());
await repo.completeTask('task-1');
final task = await repo.getTaskById('task-1');
expect(task!.status, TaskStatus.done);
});
test('should create TaskCompletion record when completeTask is called', () async {
await repo.insertTask(_makeTask(title: 'Buy milk', energyLevel: 1));
await repo.completeTask('task-1');
final completions = await db.select(db.taskCompletions).get();
expect(completions.length, 1);
expect(completions.first.taskId, 'task-1');
expect(completions.first.title, 'Buy milk');
expect(completions.first.energyLevel, 1);
});
test('should throw ArgumentError when completeTask is called with unknown id', () async {
expect(
() => repo.completeTask('nonexistent'),
throwsA(isA<ArgumentError>()),
);
});
test('should not include other users tasks in getAllTasks', () async {
await UserRepository(db).createUser(
id: 'user-2',
deviceId: 'dev-2',
name: 'Other',
petType: PetType.dog,
petName: 'Rex',
);
await repo.insertTask(_makeTask(id: 'task-1', userId: 'user-1'));
await repo.insertTask(_makeTask(id: 'task-2', userId: 'user-2'));
final tasks = await repo.getAllTasks('user-1');
expect(tasks.length, 1);
expect(tasks.first.userId, 'user-1');
});
test('should emit updated list after insert when watchTasks is used', () async {
final stream = repo.watchTasks('user-1');
await repo.insertTask(_makeTask());
await expectLater(
stream,
emitsThrough(
predicate<List<TaskEntity>>(
(list) => list.length == 1 && list.first.id == 'task-1',
),
),
);
});
test('should emit empty list after deleteTask when watchTasks is used', () async {
await repo.insertTask(_makeTask());
final stream = repo.watchTasks('user-1');
await repo.deleteTask('task-1');
await expectLater(
stream,
emitsThrough(isEmpty),
);
});
});
}
@@ -0,0 +1,108 @@
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kittie_mobile/data/db/app_database.dart';
import 'package:kittie_mobile/data/repositories/user_repository.dart';
import 'package:kittie_mobile/domain/entities/pet_type.dart';
AppDatabase _openMemory() => AppDatabase(NativeDatabase.memory());
void main() {
late AppDatabase db;
late UserRepository repo;
setUp(() {
db = _openMemory();
repo = UserRepository(db);
});
tearDown(() async {
await db.close();
});
group('UserRepository', () {
test('should return null when no user exists', () async {
final user = await repo.getCurrentUser();
expect(user, isNull);
});
test('should return created user when getCurrentUser is called', () async {
await repo.createUser(
id: 'user-1',
deviceId: 'device-abc',
name: 'Alice',
petType: PetType.cat,
petName: 'Yoshi',
);
final user = await repo.getCurrentUser();
expect(user, isNotNull);
expect(user!.id, 'user-1');
expect(user.deviceId, 'device-abc');
expect(user.name, 'Alice');
expect(user.petType, PetType.cat);
expect(user.petName, 'Yoshi');
});
test('should map dog petType correctly when creating user', () async {
await repo.createUser(
id: 'u1',
deviceId: 'dev-1',
name: 'Bob',
petType: PetType.dog,
petName: 'Rex',
);
final user = await repo.getCurrentUser();
expect(user!.petType, PetType.dog);
});
test('should map capybara petType correctly when creating user', () async {
await repo.createUser(
id: 'u1',
deviceId: 'dev-1',
name: 'Carol',
petType: PetType.capybara,
petName: 'Cappi',
);
final user = await repo.getCurrentUser();
expect(user!.petType, PetType.capybara);
});
test('should persist name and petName changes when updateUser is called', () async {
await repo.createUser(
id: 'user-1',
deviceId: 'device-abc',
name: 'Alice',
petType: PetType.dog,
petName: 'Rex',
);
final created = await repo.getCurrentUser();
final updated = created!.copyWith(name: 'Bob', petName: 'Buddy');
await repo.updateUser(updated);
final fetched = await repo.getCurrentUser();
expect(fetched!.name, 'Bob');
expect(fetched.petName, 'Buddy');
});
test('should persist petType change when updateUser is called', () async {
await repo.createUser(
id: 'user-1',
deviceId: 'device-abc',
name: 'Alice',
petType: PetType.cat,
petName: 'Yoshi',
);
final created = await repo.getCurrentUser();
final updated = created!.copyWith(petType: PetType.capybara);
await repo.updateUser(updated);
final fetched = await repo.getCurrentUser();
expect(fetched!.petType, PetType.capybara);
});
});
}