focus-flow/packages/scheduler_integration_tests/integration/scheduler_flow_test.dart

121 lines
4.1 KiB
Dart

/// Exercises Scheduler Flow integration behavior for the Scheduler Integration Tests package.
library;
import 'package:drift/native.dart';
import 'package:scheduler_core/scheduler_core.dart' as core;
import 'package:scheduler_notifications/notifications.dart';
import 'package:scheduler_persistence_memory/persistence_memory.dart';
import 'package:scheduler_persistence_sqlite/sqlite.dart';
import 'package:test/test.dart';
void main() {
test('scheduler flow schedules, notifies, persists, and reloads', () async {
final runtime = Stopwatch()..start();
addTearDown(() {
runtime.stop();
expect(runtime.elapsed, lessThan(const Duration(seconds: 2)));
});
final ownerId = core.OwnerId('owner-a');
final now = DateTime.utc(2026, 6, 26, 8);
final window = core.SchedulingWindow(
start: DateTime.utc(2026, 6, 26, 9),
end: DateTime.utc(2026, 6, 26, 10, 30),
);
final lockedInterval = core.TimeInterval(
start: DateTime.utc(2026, 6, 26, 9),
end: DateTime.utc(2026, 6, 26, 9, 15),
label: 'Protected setup time',
);
final backlogTask = core.Task(
id: 'backlog-a',
title: 'Write planning note',
projectId: 'project-a',
type: core.TaskType.flexible,
status: core.TaskStatus.backlog,
durationMinutes: 15,
createdAt: now,
updatedAt: now,
);
final plannedTask = core.Task(
id: 'planned-a',
title: 'Existing focus block',
projectId: 'project-a',
type: core.TaskType.flexible,
status: core.TaskStatus.planned,
durationMinutes: 30,
scheduledStart: DateTime.utc(2026, 6, 26, 9, 30),
scheduledEnd: DateTime.utc(2026, 6, 26, 10),
createdAt: now,
updatedAt: now,
);
final memoryTasks = InMemoryTaskRepository();
expect(
(await memoryTasks.insert(ownerId: ownerId, task: backlogTask)).isRight,
isTrue,
);
expect(
(await memoryTasks.insert(ownerId: ownerId, task: plannedTask)).isRight,
isTrue,
);
final loaded = await memoryTasks.findByOwner(ownerId: ownerId);
expect(loaded.isRight, isTrue);
final result =
const core.SchedulingEngine().insertBacklogTaskIntoNextAvailableSlot(
input: core.SchedulingInput(
tasks: loaded.right.items,
window: window,
lockedIntervals: [lockedInterval],
),
taskId: backlogTask.id,
updatedAt: now,
);
expect(result.outcomeCode, core.SchedulingOutcomeCode.success);
final scheduledBacklog = _taskById(result.tasks, backlogTask.id);
expect(scheduledBacklog.status, core.TaskStatus.planned);
expect(scheduledBacklog.scheduledStart, lockedInterval.end);
expect(
scheduledBacklog.scheduledEnd,
DateTime.utc(2026, 6, 26, 9, 30),
);
final notifications = FakeNotificationAdapter();
addTearDown(notifications.close);
await notifications.schedule(NotificationRequest(
id: 'task-${scheduledBacklog.id}-start',
title: scheduledBacklog.title,
body: 'Scheduled task is ready.',
scheduledDateTimeUtc: scheduledBacklog.scheduledStart!,
payloadJson: '{"taskId":"${scheduledBacklog.id}"}',
));
expect(
notifications.scheduledRequests.keys,
contains('task-backlog-a-start'),
);
final db = SchedulerDb(NativeDatabase.memory());
addTearDown(db.close);
final sqliteTasks = SqliteTaskRepository(db);
for (final task in result.tasks) {
final inserted = await sqliteTasks.insert(ownerId: ownerId, task: task);
expect(inserted.isRight, isTrue);
}
final reloaded = await sqliteTasks.findById(
ownerId: ownerId,
taskId: backlogTask.id,
);
expect(reloaded.isRight, isTrue);
final persistedBacklog = reloaded.right;
expect(persistedBacklog, isNotNull);
expect(persistedBacklog!.scheduledStart, scheduledBacklog.scheduledStart);
expect(persistedBacklog.scheduledEnd, scheduledBacklog.scheduledEnd);
expect(persistedBacklog.status, core.TaskStatus.planned);
});
}
core.Task _taskById(Iterable<core.Task> tasks, String id) {
return tasks.singleWhere((task) => task.id == id);
}