tests: tests for dart repositories

This commit is contained in:
Jean Jacques Avril 2025-01-04 16:25:00 +00:00
parent bdfb08f092
commit d4bf238f50
No known key found for this signature in database
16 changed files with 1493 additions and 37 deletions

View File

@ -35,4 +35,9 @@ class ProjectRepositoryImpl implements ProjectRepository {
TaskEither<IError, List<Project>> findAll() { TaskEither<IError, List<Project>> findAll() {
return database.projects.findAll(); return database.projects.findAll();
} }
@override
TaskEither<IError, List<Project>> findByUserId(String userId) {
return database.projects.findByUserId(userId);
}
} }

View File

@ -9,8 +9,8 @@ class User with _$User {
required String name, required String name,
required String email, required String email,
String? passwordHash, String? passwordHash,
DateTime? createdAt, required DateTime createdAt,
DateTime? updatedAt, required DateTime updatedAt,
}) = _User; }) = _User;
} }

View File

@ -20,8 +20,8 @@ mixin _$User {
String get name => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError;
String get email => throw _privateConstructorUsedError; String get email => throw _privateConstructorUsedError;
String? get passwordHash => throw _privateConstructorUsedError; String? get passwordHash => throw _privateConstructorUsedError;
DateTime? get createdAt => throw _privateConstructorUsedError; DateTime get createdAt => throw _privateConstructorUsedError;
DateTime? get updatedAt => throw _privateConstructorUsedError; DateTime get updatedAt => throw _privateConstructorUsedError;
/// Create a copy of User /// Create a copy of User
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -39,8 +39,8 @@ abstract class $UserCopyWith<$Res> {
String name, String name,
String email, String email,
String? passwordHash, String? passwordHash,
DateTime? createdAt, DateTime createdAt,
DateTime? updatedAt}); DateTime updatedAt});
} }
/// @nodoc /// @nodoc
@ -62,8 +62,8 @@ class _$UserCopyWithImpl<$Res, $Val extends User>
Object? name = null, Object? name = null,
Object? email = null, Object? email = null,
Object? passwordHash = freezed, Object? passwordHash = freezed,
Object? createdAt = freezed, Object? createdAt = null,
Object? updatedAt = freezed, Object? updatedAt = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
id: null == id id: null == id
@ -82,14 +82,14 @@ class _$UserCopyWithImpl<$Res, $Val extends User>
? _value.passwordHash ? _value.passwordHash
: passwordHash // ignore: cast_nullable_to_non_nullable : passwordHash // ignore: cast_nullable_to_non_nullable
as String?, as String?,
createdAt: freezed == createdAt createdAt: null == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime,
updatedAt: freezed == updatedAt updatedAt: null == updatedAt
? _value.updatedAt ? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime,
) as $Val); ) as $Val);
} }
} }
@ -106,8 +106,8 @@ abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> {
String name, String name,
String email, String email,
String? passwordHash, String? passwordHash,
DateTime? createdAt, DateTime createdAt,
DateTime? updatedAt}); DateTime updatedAt});
} }
/// @nodoc /// @nodoc
@ -126,8 +126,8 @@ class __$$UserImplCopyWithImpl<$Res>
Object? name = null, Object? name = null,
Object? email = null, Object? email = null,
Object? passwordHash = freezed, Object? passwordHash = freezed,
Object? createdAt = freezed, Object? createdAt = null,
Object? updatedAt = freezed, Object? updatedAt = null,
}) { }) {
return _then(_$UserImpl( return _then(_$UserImpl(
id: null == id id: null == id
@ -146,14 +146,14 @@ class __$$UserImplCopyWithImpl<$Res>
? _value.passwordHash ? _value.passwordHash
: passwordHash // ignore: cast_nullable_to_non_nullable : passwordHash // ignore: cast_nullable_to_non_nullable
as String?, as String?,
createdAt: freezed == createdAt createdAt: null == createdAt
? _value.createdAt ? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime,
updatedAt: freezed == updatedAt updatedAt: null == updatedAt
? _value.updatedAt ? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime,
)); ));
} }
} }
@ -166,8 +166,8 @@ class _$UserImpl implements _User {
required this.name, required this.name,
required this.email, required this.email,
this.passwordHash, this.passwordHash,
this.createdAt, required this.createdAt,
this.updatedAt}); required this.updatedAt});
@override @override
final String id; final String id;
@ -178,9 +178,9 @@ class _$UserImpl implements _User {
@override @override
final String? passwordHash; final String? passwordHash;
@override @override
final DateTime? createdAt; final DateTime createdAt;
@override @override
final DateTime? updatedAt; final DateTime updatedAt;
@override @override
String toString() { String toString() {
@ -222,8 +222,8 @@ abstract class _User implements User {
required final String name, required final String name,
required final String email, required final String email,
final String? passwordHash, final String? passwordHash,
final DateTime? createdAt, required final DateTime createdAt,
final DateTime? updatedAt}) = _$UserImpl; required final DateTime updatedAt}) = _$UserImpl;
@override @override
String get id; String get id;
@ -234,9 +234,9 @@ abstract class _User implements User {
@override @override
String? get passwordHash; String? get passwordHash;
@override @override
DateTime? get createdAt; DateTime get createdAt;
@override @override
DateTime? get updatedAt; DateTime get updatedAt;
/// Create a copy of User /// Create a copy of User
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.

View File

@ -9,6 +9,9 @@ abstract class ProjectRepository {
/// Finds a project by its unique ID. /// Finds a project by its unique ID.
TaskEither<IError, Project> findById(String id); TaskEither<IError, Project> findById(String id);
// Finds all projects by a user's unique ID.
TaskEither<IError, List<Project>> findByUserId(String userId);
/// Updates an existing project. /// Updates an existing project.
TaskEither<IError, Project> update(ProjectUpdate project); TaskEither<IError, Project> update(ProjectUpdate project);

View File

@ -13,8 +13,8 @@ class UserDboMapper {
name: target.name!, name: target.name!,
email: target.email!, email: target.email!,
passwordHash: target.password, passwordHash: target.password,
createdAt: target.createdAt, createdAt: target.createdAt!,
updatedAt: target.updatedAt, updatedAt: target.updatedAt!,
)); ));
TaskEither<IError, List<User>> listFrom(Iterable<UserDbo> targets) { TaskEither<IError, List<User>> listFrom(Iterable<UserDbo> targets) {

View File

@ -786,26 +786,26 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: test name: test
sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" sha256: "8391fbe68d520daf2314121764d38e37f934c02fd7301ad18307bd93bd6b725d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.25.8" version: "1.25.14"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.3" version: "0.7.4"
test_core: test_core:
dependency: transitive dependency: transitive
description: description:
name: test_core name: test_core
sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.5" version: "0.6.8"
timing: timing:
dependency: transitive dependency: transitive
description: description:

View File

@ -26,7 +26,7 @@ dependencies:
dev_dependencies: dev_dependencies:
lints: ^3.0.0 lints: ^3.0.0
test: ^1.24.0 test: ^1.25.14
shelf_router_generator: ^1.1.0 shelf_router_generator: ^1.1.0
build_runner: ^2.4.14 build_runner: ^2.4.14
riverpod_generator: ^2.6.3 riverpod_generator: ^2.6.3

View File

@ -0,0 +1,45 @@
import 'package:backend_dart/domain/data/database.dart';
import 'package:backend_dart/domain/data/project_data_source.dart';
import 'package:backend_dart/domain/data/project_task_data_source.dart';
import 'package:backend_dart/domain/data/time_entry_data_source.dart';
import 'package:backend_dart/domain/data/user_data_source.dart';
import 'package:backend_dart/domain/entities/project.dart';
import 'package:backend_dart/domain/entities/project_task.dart';
import 'package:backend_dart/domain/entities/time_entry.dart';
import 'package:backend_dart/domain/entities/user.dart';
import 'mock_project_data_source.dart';
import 'mock_project_task_data_source.dart';
import 'mock_time_entry_data_source.dart';
import 'mock_user_data_source.dart';
class MockDatabase implements IDatabase {
final Map<String, User> _usersStore = {};
final Map<String, ProjectTask> _tasksStore = {};
final Map<String, Project> _projectsStore = {};
final Map<String, TimeEntry> _timeEntriesStore = {};
@override
Future<void> close() {
throw UnimplementedError();
}
@override
ProjectDataSource get projects => MockProjectDataSource(_projectsStore);
@override
ProjectTaskDataSource get tasks => MockProjectTaskDataSource(_tasksStore);
@override
TimeEntryDataSource get timeEntries =>
MockTimeEntryDataSource(_timeEntriesStore);
@override
UserDataSource get users => MockUserDataSource(_usersStore);
void clear() {
_usersStore.clear();
_tasksStore.clear();
_projectsStore.clear();
_timeEntriesStore.clear();
}
}

View File

@ -0,0 +1,137 @@
import 'package:backend_dart/domain/data/project_data_source.dart';
import 'package:backend_dart/domain/entities/project.dart';
import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/errors/error.dart';
import 'package:fpdart/fpdart.dart';
import 'package:uuid/uuid.dart';
class MockProjectDataSource implements ProjectDataSource {
MockProjectDataSource(this._store);
final Map<String, Project> _store;
final Uuid _uuid = Uuid();
@override
TaskEither<IError, Project> create(ProjectCreate project) {
return TaskEither.tryCatch(
() async {
final id = _uuid.v4();
final newProject = Project(
id: id,
userId: project.userId,
name: project.name,
description: project.description,
clientId: project.clientId,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
_store[id] = newProject;
return newProject;
},
(error, _) => AppError.databaseError(
message: 'Failed to create project: ${error.toString()}',
),
);
}
@override
TaskEither<IError, Project> findById(String id) {
return TaskEither.tryCatch(
() async {
final project = _store[id];
if (project == null) {
throw AppError.notFound('Project with ID $id not found');
}
return project;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to find project by ID: ${error.toString()}',
),
);
}
@override
TaskEither<IError, List<Project>> findByUserId(String userId) {
return TaskEither.tryCatch(
() async {
final projects =
_store.values.where((project) => project.userId == userId).toList();
return projects;
},
(error, _) => AppError.databaseError(
message:
'Failed to fetch projects for user $userId: ${error.toString()}',
),
);
}
@override
TaskEither<IError, Project> update(ProjectUpdate project) {
return TaskEither.tryCatch(
() async {
final existingProject = _store[project.id];
if (existingProject == null) {
throw AppError.notFound('Project with ID ${project.id} not found');
}
final updatedProject = existingProject.copyWith(
name: project.name ?? existingProject.name,
description: project.description ?? existingProject.description,
clientId: project.clientId ?? existingProject.clientId,
updatedAt: DateTime.now(),
);
_store[project.id] = updatedProject;
return updatedProject;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to update project: ${error.toString()}',
),
);
}
@override
TaskEither<IError, Project> delete(String id) {
return TaskEither.tryCatch(
() async {
final project = _store.remove(id);
if (project == null) {
throw AppError.notFound('Project with ID $id not found');
}
return project;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to delete project: ${error.toString()}',
),
);
}
@override
TaskEither<IError, List<Project>> findAll() {
return TaskEither.tryCatch(
() async => _store.values.toList(),
(error, _) => AppError.databaseError(
message: 'Failed to fetch all projects: ${error.toString()}',
),
);
}
@override
TaskEither<IError, String> generateId() {
return TaskEither.tryCatch(
() async {
String id;
do {
id = _uuid.v4();
} while (_store.containsKey(id));
return id;
},
(error, _) => AppError.databaseError(
message: 'Failed to generate ID: ${error.toString()}',
),
);
}
}

View File

@ -0,0 +1,135 @@
import 'package:backend_dart/domain/data/project_task_data_source.dart';
import 'package:backend_dart/domain/entities/project_task.dart';
import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/errors/error.dart';
import 'package:fpdart/fpdart.dart';
import 'package:uuid/uuid.dart';
class MockProjectTaskDataSource implements ProjectTaskDataSource {
MockProjectTaskDataSource(this._store);
final Map<String, ProjectTask> _store;
final Uuid _uuid = Uuid();
@override
TaskEither<IError, ProjectTask> create(ProjectTaskCreate task) {
return TaskEither.tryCatch(
() async {
final id = _uuid.v4();
final newTask = ProjectTask(
id: id,
projectId: task.projectId,
name: task.name,
description: task.description,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
_store[id] = newTask;
return newTask;
},
(error, _) => AppError.databaseError(
message: 'Failed to create project task: ${error.toString()}',
),
);
}
@override
TaskEither<IError, ProjectTask> findById(String id) {
return TaskEither.tryCatch(
() async {
final task = _store[id];
if (task == null) {
throw AppError.notFound('Project task with ID $id not found');
}
return task;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to find project task by ID: ${error.toString()}',
),
);
}
@override
TaskEither<IError, List<ProjectTask>> findByProjectId(String projectId) {
return TaskEither.tryCatch(
() async {
final tasks =
_store.values.where((task) => task.projectId == projectId).toList();
return tasks;
},
(error, _) => AppError.databaseError(
message:
'Failed to fetch tasks for project $projectId: ${error.toString()}',
),
);
}
@override
TaskEither<IError, ProjectTask> update(ProjectTaskUpdate task) {
return TaskEither.tryCatch(
() async {
final existingTask = _store[task.id];
if (existingTask == null) {
throw AppError.notFound('Project task with ID ${task.id} not found');
}
final updatedTask = existingTask.copyWith(
name: task.name ?? existingTask.name,
description: task.description ?? existingTask.description,
updatedAt: DateTime.now(),
);
_store[task.id] = updatedTask;
return updatedTask;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to update project task: ${error.toString()}',
),
);
}
@override
TaskEither<IError, ProjectTask> delete(String id) {
return TaskEither.tryCatch(
() async {
final task = _store.remove(id);
if (task == null) {
throw AppError.notFound('Project task with ID $id not found');
}
return task;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to delete project task: ${error.toString()}',
),
);
}
@override
TaskEither<IError, List<ProjectTask>> findAll() {
return TaskEither.tryCatch(
() async => _store.values.toList(),
(error, _) => AppError.databaseError(
message: 'Failed to fetch all project tasks: ${error.toString()}',
),
);
}
@override
TaskEither<IError, String> generateId() {
return TaskEither.tryCatch(
() async {
String id;
do {
id = _uuid.v4();
} while (_store.containsKey(id));
return id;
},
(error, _) => AppError.databaseError(
message: 'Failed to generate ID: ${error.toString()}',
),
);
}
}

View File

@ -0,0 +1,156 @@
import 'package:backend_dart/domain/data/time_entry_data_source.dart';
import 'package:backend_dart/domain/entities/time_entry.dart';
import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/errors/error.dart';
import 'package:fpdart/fpdart.dart';
import 'package:uuid/uuid.dart';
class MockTimeEntryDataSource implements TimeEntryDataSource {
MockTimeEntryDataSource(this._store);
final Map<String, TimeEntry> _store;
final Uuid _uuid = Uuid();
@override
TaskEither<IError, TimeEntry> create(TimeEntryCreate timeEntry) {
return TaskEither.tryCatch(
() async {
final id = _uuid.v4();
final newEntry = TimeEntry(
id: id,
userId: timeEntry.userId,
projectId: timeEntry.projectId,
startTime: timeEntry.startTime,
endTime: timeEntry.endTime,
description: timeEntry.description,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
_store[id] = newEntry;
return newEntry;
},
(error, _) => AppError.databaseError(
message: 'Failed to create time entry: ${error.toString()}',
),
);
}
@override
TaskEither<IError, TimeEntry> findById(String id) {
return TaskEither.tryCatch(
() async {
final entry = _store[id];
if (entry == null) {
throw AppError.notFound('Time entry with ID $id not found');
}
return entry;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to find time entry by ID: ${error.toString()}',
),
);
}
@override
TaskEither<IError, List<TimeEntry>> findByUserId(String userId) {
return TaskEither.tryCatch(
() async {
final entries =
_store.values.where((entry) => entry.userId == userId).toList();
return entries;
},
(error, _) => AppError.databaseError(
message:
'Failed to fetch time entries for user $userId: ${error.toString()}',
),
);
}
@override
TaskEither<IError, List<TimeEntry>> findByProjectId(String projectId) {
return TaskEither.tryCatch(
() async {
final entries = _store.values
.where((entry) => entry.projectId == projectId)
.toList();
return entries;
},
(error, _) => AppError.databaseError(
message:
'Failed to fetch time entries for project $projectId: ${error.toString()}',
),
);
}
@override
TaskEither<IError, TimeEntry> update(TimeEntryUpdate timeEntry) {
return TaskEither.tryCatch(
() async {
final existingEntry = _store[timeEntry.id];
if (existingEntry == null) {
throw AppError.notFound(
'Time entry with ID ${timeEntry.id} not found');
}
final updatedEntry = existingEntry.copyWith(
startTime: timeEntry.startTime ?? existingEntry.startTime,
endTime: timeEntry.endTime ?? existingEntry.endTime,
description: timeEntry.description ?? existingEntry.description,
projectId: timeEntry.projectId ?? existingEntry.projectId,
updatedAt: DateTime.now(),
);
_store[timeEntry.id] = updatedEntry;
return updatedEntry;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to update time entry: ${error.toString()}',
),
);
}
@override
TaskEither<IError, TimeEntry> delete(String id) {
return TaskEither.tryCatch(
() async {
final entry = _store.remove(id);
if (entry == null) {
throw AppError.notFound('Time entry with ID $id not found');
}
return entry;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to delete time entry: ${error.toString()}',
),
);
}
@override
TaskEither<IError, List<TimeEntry>> findAll() {
return TaskEither.tryCatch(
() async => _store.values.toList(),
(error, _) => AppError.databaseError(
message: 'Failed to fetch all time entries: ${error.toString()}',
),
);
}
@override
TaskEither<IError, String> generateId() {
return TaskEither.tryCatch(
() async {
String id;
do {
id = _uuid.v4();
} while (_store.containsKey(id));
return id;
},
(error, _) => AppError.databaseError(
message: 'Failed to generate ID: ${error.toString()}',
),
);
}
}

View File

@ -0,0 +1,144 @@
import 'package:backend_dart/common/extensions.dart';
import 'package:backend_dart/common/secure_hash.dart';
import 'package:backend_dart/domain/data/user_data_source.dart';
import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/errors/app_error.dart';
import 'package:backend_dart/domain/errors/error.dart';
import 'package:fpdart/fpdart.dart';
import 'package:uuid/uuid.dart';
class MockUserDataSource implements UserDataSource {
MockUserDataSource(this.store);
final Map<String, User> store;
final Uuid _uuid = Uuid();
@override
TaskEither<IError, User> create(UserCreate user) {
return TaskEither.tryCatch(
() async {
final id = _uuid.v4();
final newUser = User(
id: id,
email: user.email,
passwordHash: generateSecureHash(user.password),
name: user.name,
createdAt: DateTime.now(),
updatedAt: DateTime.now());
store[id] = newUser;
return newUser;
},
(error, _) => AppError.databaseError(
message: 'Failed to create user: ${error.toString()}',
),
);
}
@override
TaskEither<IError, User> findByEmail(String email) {
return TaskEither.tryCatch(
() async {
final user = store.values
.where(
(user) => user.email == email,
)
.firstOrNull;
if (user == null) {
throw AppError.notFound('User with email $email not found');
}
return user;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to find user by email: ${error.toString()}',
),
);
}
@override
TaskEither<IError, User> findById(String id) {
return TaskEither.tryCatch(
() async {
final user = store[id];
if (user == null) {
throw AppError.notFound('User with ID $id not found');
}
return user;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to find user by ID: ${error.toString()}',
),
);
}
@override
TaskEither<IError, User> update(UserUpdate user) {
return TaskEither.tryCatch(
() async {
final existingUser = store[user.id];
if (existingUser == null) {
throw AppError.notFound('User with ID ${user.id} not found');
}
final updatedUser = existingUser.copyWith(
email: user.email ?? existingUser.email,
passwordHash: user.password.let(generateSecureHash) ??
existingUser.passwordHash,
name: user.name ?? existingUser.name,
);
store[user.id] = updatedUser;
return updatedUser;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to update user: ${error.toString()}',
),
);
}
@override
TaskEither<IError, User> delete(String id) {
return TaskEither.tryCatch(
() async {
final user = store.remove(id);
if (user == null) {
throw AppError.notFound('User with ID $id not found');
}
return user;
},
(error, _) => error is AppError
? error
: AppError.databaseError(
message: 'Failed to delete user: ${error.toString()}',
),
);
}
@override
TaskEither<IError, List<User>> findAll() {
return TaskEither.tryCatch(
() async => store.values.toList(),
(error, _) => AppError.databaseError(
message: 'Failed to fetch all users: ${error.toString()}',
),
);
}
@override
TaskEither<IError, String> generateId() {
return TaskEither.tryCatch(
() async {
String id;
do {
id = _uuid.v4();
} while (store.containsKey(id));
return id;
},
(error, _) => AppError.databaseError(
message: 'Failed to generate ID: ${error.toString()}',
),
);
}
}

View File

@ -0,0 +1,199 @@
import 'package:backend_dart/application/repository/project_repository_impl.dart';
import 'package:backend_dart/domain/entities/project.dart';
import 'package:backend_dart/domain/repository/project_repository.dart';
import 'package:fpdart/fpdart.dart';
import 'package:test/test.dart';
import 'mocks/mock_database.dart';
void main() {
late MockDatabase database;
late ProjectRepository projectRepository;
setUpAll(() {
database = MockDatabase();
projectRepository = ProjectRepositoryImpl(database);
});
setUp(() {
database.clear(); // Clear the database before each test
});
test('create project', () async {
final projectCreate = ProjectCreate(
userId: 'user123',
name: 'Test Project',
description: 'This is a test project',
clientId: 'client123',
);
final project = await projectRepository.create(projectCreate).run();
expect(project.isRight(), true, reason: 'Result should be right');
project.match(
(_) => fail('Result should be right'),
(project) {
expect(project.userId, projectCreate.userId,
reason: 'User ID should be ${projectCreate.userId}');
expect(project.name, projectCreate.name,
reason: 'Name should be ${projectCreate.name}');
expect(project.description, projectCreate.description,
reason: 'Description should be ${projectCreate.description}');
expect(project.clientId, projectCreate.clientId,
reason: 'Client ID should be ${projectCreate.clientId}');
},
);
});
test('find project by id', () async {
final projectCreate = ProjectCreate(
userId: 'user123',
name: 'Test Project',
description: 'This is a test project',
clientId: 'client123',
);
final createdProject = await projectRepository.create(projectCreate).run();
createdProject.match(
(_) => fail('Result should be right'),
(project) async {
final foundProject = await projectRepository.findById(project.id).run();
foundProject.match(
(_) => fail('Result should be right'),
(project) {
expect(project.name, projectCreate.name,
reason: 'Name should be ${projectCreate.name}');
},
);
},
);
});
test('find projects by user id', () async {
final project1 = ProjectCreate(
userId: 'user123',
name: 'Project 1',
description: 'Description 1',
clientId: 'client123',
);
final project2 = ProjectCreate(
userId: 'user123',
name: 'Project 2',
description: 'Description 2',
clientId: 'client123',
);
await projectRepository.create(project1).run();
await projectRepository.create(project2).run();
final projects = await projectRepository.findByUserId('user123').run();
projects.match(
(_) => fail('Result should be right'),
(projects) {
expect(projects.length, 2, reason: 'Should return two projects');
expect(
projects.map((p) => p.name).toList(),
containsAll([project1.name, project2.name]),
);
},
);
});
test('update project', () async {
final projectCreate = ProjectCreate(
userId: 'user123',
name: 'Old Name',
description: 'Old Description',
clientId: 'client123',
);
final createdProject = await projectRepository.create(projectCreate).run();
createdProject.match(
(_) => fail('Result should be right'),
(project) async {
final projectUpdate = ProjectUpdate(
id: project.id,
name: 'Updated Name',
description: 'Updated Description',
);
final updatedProject =
await projectRepository.update(projectUpdate).run();
updatedProject.match(
(_) => fail('Result should be right'),
(project) {
expect(project.name, 'Updated Name',
reason: 'Name should be Updated Name');
expect(project.description, 'Updated Description',
reason: 'Description should be Updated Description');
},
);
},
);
});
test('delete project', () async {
final projectCreate = ProjectCreate(
userId: 'user123',
name: 'Test Project',
description: 'This is a test project',
clientId: 'client123',
);
final createdProject = await projectRepository.create(projectCreate).run();
createdProject.match(
(_) => fail('Result should be right'),
(project) async {
final deletedProject = await projectRepository.delete(project.id).run();
deletedProject.match(
(_) => fail('Result should be right'),
(project) {
expect(project.id, createdProject.match((e) => null, identity)?.id,
reason: 'Deleted project ID should match created project ID');
},
);
final result = await projectRepository.findById(project.id).run();
expect(result.isLeft(), true, reason: 'Project should no longer exist');
},
);
});
test('find all projects', () async {
final project1 = ProjectCreate(
userId: 'user123',
name: 'Project 1',
description: 'Description 1',
clientId: 'client123',
);
final project2 = ProjectCreate(
userId: 'user456',
name: 'Project 2',
description: 'Description 2',
clientId: 'client123',
);
await projectRepository.create(project1).run();
await projectRepository.create(project2).run();
final projects = await projectRepository.findAll().run();
projects.match(
(_) => fail('Result should be right'),
(projects) {
expect(projects.length, 2, reason: 'Should return all projects');
expect(
projects.map((p) => p.name).toList(),
containsAll([project1.name, project2.name]),
);
},
);
});
}

View File

@ -0,0 +1,191 @@
import 'package:backend_dart/application/repository/project_task_repository_impl.dart';
import 'package:backend_dart/domain/entities/project_task.dart';
import 'package:backend_dart/domain/repository/project_task_repository.dart';
import 'package:fpdart/fpdart.dart';
import 'package:test/test.dart';
import 'mocks/mock_database.dart';
void main() {
late MockDatabase database;
late ProjectTaskRepository projectTaskRepository;
setUpAll(() {
database = MockDatabase();
projectTaskRepository = ProjectTaskRepositoryImpl(database);
});
setUp(() {
database.clear(); // Clear the database before each test
});
test('create project task', () async {
final projectTaskCreate = ProjectTaskCreate(
projectId: 'project123',
name: 'Test Task',
description: 'This is a test task',
);
final projectTask =
await projectTaskRepository.create(projectTaskCreate).run();
expect(projectTask.isRight(), true, reason: 'Result should be right');
projectTask.match(
(_) => fail('Result should be right'),
(task) {
expect(task.projectId, projectTaskCreate.projectId,
reason: 'Project ID should match');
expect(task.name, projectTaskCreate.name,
reason: 'Task name should match');
expect(task.description, projectTaskCreate.description,
reason: 'Description should match');
},
);
});
test('find project task by id', () async {
final projectTaskCreate = ProjectTaskCreate(
projectId: 'project123',
name: 'Test Task',
description: 'This is a test task',
);
final createdTask =
await projectTaskRepository.create(projectTaskCreate).run();
createdTask.match(
(_) => fail('Result should be right'),
(task) async {
final foundTask = await projectTaskRepository.findById(task.id).run();
foundTask.match(
(_) => fail('Result should be right'),
(task) {
expect(task.id, createdTask.match((e) => null, identity)?.id,
reason: 'Task ID should match');
},
);
},
);
});
test('find tasks by project id', () async {
final task1 = ProjectTaskCreate(
projectId: 'project123',
name: 'Task 1',
description: 'First task',
);
final task2 = ProjectTaskCreate(
projectId: 'project123',
name: 'Task 2',
description: 'Second task',
);
await projectTaskRepository.create(task1).run();
await projectTaskRepository.create(task2).run();
final tasks =
await projectTaskRepository.findByProjectId('project123').run();
tasks.match(
(_) => fail('Result should be right'),
(tasks) {
expect(tasks.length, 2, reason: 'Should return two tasks');
expect(
tasks.map((t) => t.name).toList(),
containsAll([task1.name, task2.name]),
);
},
);
});
test('update project task', () async {
final projectTaskCreate = ProjectTaskCreate(
projectId: 'project123',
name: 'Initial Task',
description: 'Initial Description',
);
final createdTask =
await projectTaskRepository.create(projectTaskCreate).run();
createdTask.match(
(_) => fail('Result should be right'),
(task) async {
final taskUpdate = ProjectTaskUpdate(
id: task.id,
name: 'Updated Task',
);
final updatedTask =
await projectTaskRepository.update(taskUpdate).run();
updatedTask.match(
(_) => fail('Result should be right'),
(task) {
expect(task.name, 'Updated Task',
reason: 'Task name should be updated');
},
);
},
);
});
test('delete project task', () async {
final projectTaskCreate = ProjectTaskCreate(
projectId: 'project123',
name: 'Task to delete',
description: 'Task description',
);
final createdTask =
await projectTaskRepository.create(projectTaskCreate).run();
createdTask.match(
(_) => fail('Result should be right'),
(task) async {
final deletedTask = await projectTaskRepository.delete(task.id).run();
deletedTask.match(
(_) => fail('Result should be right'),
(task) {
expect(task.id, createdTask.match((e) => null, identity)?.id,
reason: 'Deleted task ID should match created task ID');
},
);
final result = await projectTaskRepository.findById(task.id).run();
expect(result.isLeft(), true, reason: 'Task should no longer exist');
},
);
});
test('find all project tasks', () async {
final task1 = ProjectTaskCreate(
projectId: 'project123',
name: 'Task 1',
description: 'Description 1',
);
final task2 = ProjectTaskCreate(
projectId: 'project456',
name: 'Task 2',
description: 'Description 2',
);
await projectTaskRepository.create(task1).run();
await projectTaskRepository.create(task2).run();
final tasks = await projectTaskRepository.findAll().run();
tasks.match(
(_) => fail('Result should be right'),
(tasks) {
expect(tasks.length, 2, reason: 'Should return all tasks');
expect(
tasks.map((t) => t.name),
containsAll([task1.name, task2.name]),
);
},
);
});
}

View File

@ -0,0 +1,239 @@
import 'package:backend_dart/application/repository/time_entry_repository_impl.dart';
import 'package:backend_dart/domain/entities/time_entry.dart';
import 'package:backend_dart/domain/repository/time_entry_repository.dart';
import 'package:fpdart/fpdart.dart';
import 'package:test/test.dart';
import 'mocks/mock_database.dart';
void main() {
late MockDatabase database;
late TimeEntryRepository timeEntryRepository;
setUpAll(() {
database = MockDatabase();
timeEntryRepository = TimeEntryRepositoryImpl(database);
});
setUp(() {
database.clear(); // Clear the database before each test
});
test('create time entry', () async {
final timeEntryCreate = TimeEntryCreate(
userId: 'user123',
projectId: 'project123',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 1)),
description: 'Working on project',
);
final timeEntry = await timeEntryRepository.create(timeEntryCreate).run();
expect(timeEntry.isRight(), true, reason: 'Result should be right');
timeEntry.match(
(_) => fail('Result should be right'),
(timeEntry) {
expect(timeEntry.userId, timeEntryCreate.userId,
reason: 'User ID should match');
expect(timeEntry.projectId, timeEntryCreate.projectId,
reason: 'Project ID should match');
expect(timeEntry.startTime, timeEntryCreate.startTime,
reason: 'Start time should match');
expect(timeEntry.endTime, timeEntryCreate.endTime,
reason: 'End time should match');
expect(timeEntry.description, timeEntryCreate.description,
reason: 'Description should match');
},
);
});
test('find time entry by id', () async {
final timeEntryCreate = TimeEntryCreate(
userId: 'user123',
projectId: 'project123',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 1)),
description: 'Working on project',
);
final createdEntry =
await timeEntryRepository.create(timeEntryCreate).run();
createdEntry.match(
(_) => fail('Result should be right'),
(entry) async {
final foundEntry = await timeEntryRepository.findById(entry.id).run();
foundEntry.match(
(_) => fail('Result should be right'),
(entry) {
expect(entry.id, createdEntry.match((e) => null, identity)?.id,
reason: 'ID should match the created entry');
},
);
},
);
});
test('find time entries by user id', () async {
final timeEntry1 = TimeEntryCreate(
userId: 'user123',
projectId: 'project123',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 1)),
description: 'Task 1',
);
final timeEntry2 = TimeEntryCreate(
userId: 'user123',
projectId: 'project456',
startTime: DateTime.now().add(Duration(hours: 2)),
endTime: DateTime.now().add(Duration(hours: 3)),
description: 'Task 2',
);
await timeEntryRepository.create(timeEntry1).run();
await timeEntryRepository.create(timeEntry2).run();
final entries = await timeEntryRepository.findByUserId('user123').run();
entries.match(
(_) => fail('Result should be right'),
(entries) {
expect(entries.length, 2, reason: 'Should return two entries');
expect(entries.map((e) => e.description),
containsAll([timeEntry1.description, timeEntry2.description]));
},
);
});
test('find time entries by project id', () async {
final timeEntry1 = TimeEntryCreate(
userId: 'user123',
projectId: 'project123',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 1)),
description: 'Task 1',
);
final timeEntry2 = TimeEntryCreate(
userId: 'user456',
projectId: 'project123',
startTime: DateTime.now().add(Duration(hours: 2)),
endTime: DateTime.now().add(Duration(hours: 3)),
description: 'Task 2',
);
await timeEntryRepository.create(timeEntry1).run();
await timeEntryRepository.create(timeEntry2).run();
final entries =
await timeEntryRepository.findByProjectId('project123').run();
entries.match(
(_) => fail('Result should be right'),
(entries) {
expect(entries.length, 2, reason: 'Should return two entries');
expect(entries.map((e) => e.description),
containsAll([timeEntry1.description, timeEntry2.description]));
},
);
});
test('update time entry', () async {
final timeEntryCreate = TimeEntryCreate(
userId: 'user123',
projectId: 'project123',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 1)),
description: 'Initial Task',
);
final createdEntry =
await timeEntryRepository.create(timeEntryCreate).run();
createdEntry.match(
(_) => fail('Result should be right'),
(entry) async {
final timeEntryUpdate = TimeEntryUpdate(
id: entry.id,
description: 'Updated Task',
);
final updatedEntry =
await timeEntryRepository.update(timeEntryUpdate).run();
updatedEntry.match(
(_) => fail('Result should be right'),
(entry) {
expect(entry.description, 'Updated Task',
reason: 'Description should be updated');
},
);
},
);
});
test('delete time entry', () async {
final timeEntryCreate = TimeEntryCreate(
userId: 'user123',
projectId: 'project123',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 1)),
description: 'Task to delete',
);
final createdEntry =
await timeEntryRepository.create(timeEntryCreate).run();
createdEntry.match(
(_) => fail('Result should be right'),
(entry) async {
final deletedEntry = await timeEntryRepository.delete(entry.id).run();
deletedEntry.match(
(_) => fail('Result should be right'),
(entry) {
expect(entry.id, createdEntry.match((e) => null, identity)?.id,
reason: 'Deleted entry ID should match created entry ID');
},
);
final result = await timeEntryRepository.findById(entry.id).run();
expect(result.isLeft(), true, reason: 'Entry should no longer exist');
},
);
});
test('find all time entries', () async {
final timeEntry1 = TimeEntryCreate(
userId: 'user123',
projectId: 'project123',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 1)),
description: 'Task 1',
);
final timeEntry2 = TimeEntryCreate(
userId: 'user456',
projectId: 'project456',
startTime: DateTime.now().add(Duration(hours: 2)),
endTime: DateTime.now().add(Duration(hours: 3)),
description: 'Task 2',
);
await timeEntryRepository.create(timeEntry1).run();
await timeEntryRepository.create(timeEntry2).run();
final entries = await timeEntryRepository.findAll().run();
entries.match(
(_) => fail('Result should be right'),
(entries) {
expect(entries.length, 2, reason: 'Should return all entries');
expect(
entries.map((e) => e.description),
containsAll([timeEntry1.description, timeEntry2.description]),
);
},
);
});
}

View File

@ -0,0 +1,202 @@
import 'package:backend_dart/application/repository/user_repository_impl.dart';
import 'package:backend_dart/common/secure_hash.dart';
import 'package:backend_dart/domain/entities/user.dart';
import 'package:backend_dart/domain/repository/user_repository.dart';
import 'package:fpdart/fpdart.dart';
import 'package:test/test.dart';
import 'mocks/mock_database.dart';
void main() {
late MockDatabase database;
late UserRepository userRepository;
setUpAll(() {
database = MockDatabase();
userRepository = UserRepositoryImpl(database);
});
setUp(() {
database.clear(); // Für jeden Test die Datenbank zurücksetzen
});
test('generateSecureHash', () {
final password = 'password';
final hash = generateSecureHash(password);
final expected =
'5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8';
expect(hash, expected, reason: 'Hash should be $expected');
});
test('create user', () async {
database.clear();
final userCreate = UserCreate(
email: 'user@example.com',
password: 'password',
name: 'John Doe1',
);
final user = await userRepository.create(userCreate).run();
expect(user.isRight(), true, reason: 'Result should be right');
user.match(
(_) => fail('Result should be right'),
(user) {
final hashedPassword = generateSecureHash(userCreate.password);
expect(user.email, userCreate.email,
reason: 'Email should be ${userCreate.email}');
expect(user.name, userCreate.name,
reason: 'Name should be ${userCreate.name}');
expect(user.passwordHash, hashedPassword,
reason: 'Password hash should be $hashedPassword');
},
);
});
test('find user by email', () async {
database.clear();
final userCreate = UserCreate(
email: 'user@example.com',
password: 'password',
name: 'John Doe2',
);
await userRepository.create(userCreate).run();
final foundUser = await userRepository.findByEmail(userCreate.email).run();
foundUser.match(
(_) => fail('Result should be right'),
(user) {
final hashedPassword = generateSecureHash(userCreate.password);
expect(user.email, userCreate.email,
reason: 'Email should be ${userCreate.email}');
expect(user.name, userCreate.name,
reason: 'Name should be ${userCreate.name}');
expect(user.passwordHash, hashedPassword,
reason: 'Password hash should be $hashedPassword');
},
);
});
test('find user by id', () async {
database.clear();
final userCreate = UserCreate(
email: 'user@example.com',
password: 'password',
name: 'John Doe3',
);
final createdUser = await userRepository.create(userCreate).run();
createdUser.match(
(_) => fail('Result should be right'),
(user) async {
final foundUser = await userRepository.findById(user.id).run();
foundUser.match(
(_) => fail('Result should be right'),
(user) {
final hashedPassword = generateSecureHash(userCreate.password);
expect(user.email, userCreate.email,
reason: 'Email should be ${userCreate.email}');
expect(user.name, userCreate.name,
reason: 'Name should be ${userCreate.name}');
expect(user.passwordHash, hashedPassword,
reason: 'Password hash should be $hashedPassword');
},
);
},
);
});
test('update user', () async {
database.clear();
final userCreate = UserCreate(
email: 'user@example.com',
password: 'password',
name: 'John Doe4',
);
final createdUser = await userRepository.create(userCreate).run();
createdUser.match(
(_) => fail('Result should be right'),
(user) async {
final userUpdate = UserUpdate(
id: user.id,
name: 'Jane Doe',
);
final updatedUser = await userRepository.update(userUpdate).run();
updatedUser.match(
(_) => fail('Result should be right'),
(user) {
expect(user.name, 'Jane Doe',
reason: 'Updated name should be Jane Doe');
},
);
},
);
});
test('delete user', () async {
database.clear();
final userCreate = UserCreate(
email: 'user@example.com',
password: 'password',
name: 'John Doe5',
);
final createdUser = await userRepository.create(userCreate).run();
createdUser.match(
(_) => fail('Result should be right'),
(user) async {
final deletedUser = await userRepository.delete(user.id).run();
deletedUser.match(
(_) => fail('Result should be right'),
(user) {
expect(user.id, createdUser.match((e) => null, identity)?.id,
reason: 'Deleted user ID should match created user ID');
},
);
final result = await userRepository.findById(user.id).run();
expect(result.isLeft(), true, reason: 'User should no longer exist');
},
);
});
test('find all users', () async {
database.clear();
final user1 = UserCreate(
email: 'user1@example.com',
password: 'password1',
name: 'User One',
);
final user2 = UserCreate(
email: 'user2@example.com',
password: 'password2',
name: 'User Two',
);
await userRepository.create(user1).run();
await userRepository.create(user2).run();
final result = await userRepository.findAll().run();
result.match(
(_) => fail('Result should be right'),
(users) {
print(users);
expect(users.length, 2, reason: 'There should be two users');
expect(users.map((u) => u.email).toList(),
containsAll([user1.email, user2.email]));
},
);
});
}