test: Repository tests for Drift database layer
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user