tasq/test/tasks_provider_test.dart

161 lines
4.7 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:tasq/providers/tasks_provider.dart';
// Minimal fake supabase client similar to integration test work,
// only implements the methods used by TasksController.
class _FakeClient {
final Map<String, List<Map<String, dynamic>>> tables = {
'tasks': [],
'task_activity_logs': [],
};
_FakeQuery from(String table) => _FakeQuery(this, table);
}
class _FakeQuery {
final _FakeClient client;
final String table;
Map<String, dynamic>? _eq;
Map<String, dynamic>? _insertPayload;
Map<String, dynamic>? _updatePayload;
_FakeQuery(this.client, this.table);
_FakeQuery select([String? _]) => this;
Future<Map<String, dynamic>?> maybeSingle() async {
final rows = client.tables[table] ?? [];
if (_eq != null) {
final field = _eq!.keys.first;
final value = _eq![field];
for (final r in rows) {
if (r[field] == value) {
return Map<String, dynamic>.from(r);
}
}
return null;
}
return rows.isEmpty ? null : Map<String, dynamic>.from(rows.first);
}
_FakeQuery insert(Map<String, dynamic> payload) {
_insertPayload = Map<String, dynamic>.from(payload);
return this;
}
Future<Map<String, dynamic>> single() async {
if (_insertPayload != null) {
final id = 'tsk-${client.tables['tasks']!.length + 1}';
final row = Map<String, dynamic>.from(_insertPayload!);
row['id'] = id;
client.tables[table]!.add(row);
return Map<String, dynamic>.from(row);
}
throw Exception('unexpected single() call');
}
_FakeQuery update(Map<String, dynamic> payload) {
_updatePayload = payload;
// don't apply yet; wait for eq to know which row
return this;
}
_FakeQuery eq(String field, dynamic value) {
_eq = {field: value};
// apply update payload now that we know which row to target
if (_updatePayload != null) {
final idx = client.tables[table]!.indexWhere((r) => r[field] == value);
if (idx >= 0) {
client.tables[table]![idx] = {
...client.tables[table]![idx],
..._updatePayload!,
};
}
// clear payload after applying so subsequent eq calls don't reapply
_updatePayload = null;
}
return this;
}
}
void main() {
group('TasksController business rules', () {
late _FakeClient fake;
late TasksController controller;
setUp(() {
fake = _FakeClient();
controller = TasksController(fake as dynamic);
// ignore: avoid_dynamic_calls
// note: controller expects SupabaseClient; using dynamic bypass.
});
test('cannot complete a task without required metadata', () async {
// insert a task with no metadata at all
final row = {'id': 'tsk-1', 'status': 'queued'};
fake.tables['tasks']!.add(row);
expect(
() => controller.updateTaskStatus(taskId: 'tsk-1', status: 'completed'),
throwsA(isA<Exception>()),
);
});
test('cannot complete when signatories or action taken missing', () async {
// insert a task that has the basic request metadata but nothing else
final row = {
'id': 'tsk-3',
'status': 'queued',
'request_type': 'Repair',
'request_category': 'Hardware',
};
fake.tables['tasks']!.add(row);
// still missing signatories/actionTaken
expect(
() => controller.updateTaskStatus(taskId: 'tsk-3', status: 'completed'),
throwsA(isA<Exception>()),
);
// add signatories but actionTaken still missing
await controller.updateTask(
taskId: 'tsk-3',
requestedBy: 'Alice',
notedBy: 'Bob',
receivedBy: 'Carol',
);
expect(
() => controller.updateTaskStatus(taskId: 'tsk-3', status: 'completed'),
throwsA(isA<Exception>()),
);
// add action taken (empty JSON string)
await controller.updateTask(taskId: 'tsk-3', actionTaken: '{}');
await controller.updateTaskStatus(taskId: 'tsk-3', status: 'completed');
expect(
fake.tables['tasks']!.firstWhere((t) => t['id'] == 'tsk-3')['status'],
'completed',
);
});
test('completing after adding metadata succeeds', () async {
// insert a task
final row = {'id': 'tsk-2', 'status': 'queued'};
fake.tables['tasks']!.add(row);
// update metadata via updateTask
await controller.updateTask(
taskId: 'tsk-2',
requestType: 'Repair',
requestCategory: 'Hardware',
);
await controller.updateTaskStatus(taskId: 'tsk-2', status: 'completed');
expect(
fake.tables['tasks']!.firstWhere((t) => t['id'] == 'tsk-2')['status'],
'completed',
);
});
});
}