569 lines
20 KiB
Dart
569 lines
20 KiB
Dart
import 'package:adhd_scheduler_core/scheduler_core.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('MongoDB V1 document codecs', () {
|
|
final createdAt = DateTime.utc(2026, 6, 23, 8);
|
|
final updatedAt = DateTime.utc(2026, 6, 23, 8, 5);
|
|
|
|
test('task documents round-trip full and nullable V1 fields', () {
|
|
final task = scheduledTask(
|
|
id: 'task-1',
|
|
createdAt: createdAt,
|
|
reward: RewardLevel.high,
|
|
difficulty: DifficultyLevel.hard,
|
|
).copyWith(stats: const TaskStatistics().incrementManualPush());
|
|
|
|
final document = task.toDocument(
|
|
ownerId: 'owner-1',
|
|
revision: 4,
|
|
backlogEnteredAt: createdAt,
|
|
backlogEnteredAtProvenance: 'recorded',
|
|
)..['forwardCompatibleExtra'] = 'ignored';
|
|
final restored = TaskDocumentMapping.fromDocument(document);
|
|
|
|
expect(document[DocumentFields.schemaVersion], v1SchemaVersion);
|
|
expect(document[TaskDocumentFields.ownerId], 'owner-1');
|
|
expect(document[TaskDocumentFields.revision], 4);
|
|
expect(document[TaskDocumentFields.type], 'flexible');
|
|
expect(document[TaskDocumentFields.status], 'planned');
|
|
expect(document[TaskDocumentFields.reward], 'high');
|
|
expect(document[TaskDocumentFields.difficulty], 'hard');
|
|
expect(document[TaskDocumentFields.scheduledStart],
|
|
'2026-06-23T09:00:00.000Z');
|
|
expect(restored.id, task.id);
|
|
expect(restored.type, task.type);
|
|
expect(restored.status, task.status);
|
|
expect(restored.reward, task.reward);
|
|
expect(restored.difficulty, task.difficulty);
|
|
expect(restored.scheduledStart, task.scheduledStart);
|
|
expect(restored.actualStart, task.actualStart);
|
|
expect(restored.completedAt, task.completedAt);
|
|
expect(restored.backlogTags, task.backlogTags);
|
|
expect(restored.stats.manuallyPushedCount, 1);
|
|
|
|
final nullableTask = Task(
|
|
id: 'task-nullable',
|
|
title: 'Nullable task',
|
|
projectId: 'inbox',
|
|
type: TaskType.flexible,
|
|
status: TaskStatus.backlog,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
);
|
|
final nullableDocument = nullableTask.toDocument(ownerId: 'owner-1');
|
|
final nullableRestored = TaskDocumentMapping.fromDocument(
|
|
nullableDocument,
|
|
);
|
|
|
|
expect(nullableDocument[TaskDocumentFields.priority], isNull);
|
|
expect(nullableDocument[TaskDocumentFields.durationMinutes], isNull);
|
|
expect(nullableDocument[TaskDocumentFields.scheduledStart], isNull);
|
|
expect(nullableDocument[TaskDocumentFields.backlogEnteredAt], isNull);
|
|
expect(nullableRestored.priority, isNull);
|
|
expect(nullableRestored.durationMinutes, isNull);
|
|
expect(nullableRestored.scheduledStart, isNull);
|
|
});
|
|
|
|
test('task documents preserve explicit schedule clearing', () {
|
|
final task = scheduledTask(id: 'scheduled', createdAt: createdAt);
|
|
|
|
final document = task.toDocument(
|
|
ownerId: 'owner-1',
|
|
clearSchedule: true,
|
|
);
|
|
final restored = TaskDocumentMapping.fromDocument(document);
|
|
|
|
expect(document[TaskDocumentFields.scheduledStart], isNull);
|
|
expect(document[TaskDocumentFields.scheduledEnd], isNull);
|
|
expect(restored.scheduledStart, isNull);
|
|
expect(restored.scheduledEnd, isNull);
|
|
expect(restored.actualStart, task.actualStart);
|
|
});
|
|
|
|
test('task statistics document mapping preserves every counter', () {
|
|
const stats = TaskStatistics(
|
|
skippedDuringBurnoutCount: 1,
|
|
manuallyPushedCount: 2,
|
|
autoPushedCount: 3,
|
|
movedToBacklogCount: 4,
|
|
restoredFromBacklogCount: 5,
|
|
missedCount: 6,
|
|
cancelledCount: 7,
|
|
completedLateCount: 8,
|
|
completedDuringLockedHoursCount: 9,
|
|
completedDuringLockedHoursMinutes: 10,
|
|
completedAfterShieldCount: 11,
|
|
completedAfterPushCount: 12,
|
|
totalPushesBeforeCompletion: 13,
|
|
);
|
|
|
|
final document = stats.toDocument();
|
|
final restored = TaskStatisticsDocumentMapping.fromDocument(document);
|
|
|
|
expect(document.keys.toSet(), TaskStatisticsDocumentFields.all);
|
|
expect(restored.skippedDuringBurnoutCount, 1);
|
|
expect(restored.manuallyPushedCount, 2);
|
|
expect(restored.autoPushedCount, 3);
|
|
expect(restored.movedToBacklogCount, 4);
|
|
expect(restored.restoredFromBacklogCount, 5);
|
|
expect(restored.missedCount, 6);
|
|
expect(restored.cancelledCount, 7);
|
|
expect(restored.completedLateCount, 8);
|
|
expect(restored.completedDuringLockedHoursCount, 9);
|
|
expect(restored.completedDuringLockedHoursMinutes, 10);
|
|
expect(restored.completedAfterShieldCount, 11);
|
|
expect(restored.completedAfterPushCount, 12);
|
|
expect(restored.totalPushesBeforeCompletion, 13);
|
|
});
|
|
|
|
test('project profile documents round-trip configured defaults', () {
|
|
final project = ProjectProfile(
|
|
id: 'home',
|
|
name: 'Home',
|
|
colorKey: 'home-blue',
|
|
defaultPriority: PriorityLevel.high,
|
|
defaultReward: RewardLevel.low,
|
|
defaultDifficulty: DifficultyLevel.easy,
|
|
defaultReminderProfile: ReminderProfile.persistent,
|
|
defaultDurationMinutes: 45,
|
|
archivedAt: DateTime.utc(2026, 6, 24, 9),
|
|
);
|
|
|
|
final document = ProjectDocumentMapping.toDocument(
|
|
project,
|
|
ownerId: 'owner-1',
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
revision: 2,
|
|
);
|
|
final restored = ProjectDocumentMapping.fromDocument(document);
|
|
|
|
expect(document.keys.toSet(), ProjectDocumentFields.all);
|
|
expect(document[ProjectDocumentFields.defaultPriority], 'high');
|
|
expect(
|
|
document[ProjectDocumentFields.defaultReminderProfile], 'persistent');
|
|
expect(restored.id, project.id);
|
|
expect(restored.defaultDurationMinutes, 45);
|
|
expect(restored.archivedAt, project.archivedAt);
|
|
});
|
|
|
|
test('project statistics documents preserve aggregates and applied ids',
|
|
() {
|
|
final statistics = ProjectStatistics(
|
|
projectId: 'home',
|
|
completedTaskCount: 8,
|
|
durationMinuteCounts: const {20: 3, 45: 5},
|
|
completionTimeBucketCounts: const {
|
|
ProjectCompletionTimeBucket.morning: 6,
|
|
ProjectCompletionTimeBucket.evening: 2,
|
|
},
|
|
totalPushesBeforeCompletion: 11,
|
|
completedAfterPushCount: 4,
|
|
rewardCounts: const {
|
|
RewardLevel.high: 5,
|
|
RewardLevel.low: 3,
|
|
},
|
|
difficultyCounts: const {
|
|
DifficultyLevel.easy: 2,
|
|
DifficultyLevel.hard: 6,
|
|
},
|
|
reminderProfileCounts: const {
|
|
ReminderProfile.gentle: 3,
|
|
ReminderProfile.persistent: 5,
|
|
},
|
|
);
|
|
|
|
final document = statistics.toDocument(
|
|
ownerId: 'owner-1',
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
appliedActivityIds: const ['activity-1'],
|
|
);
|
|
final restored = ProjectStatisticsDocumentMapping.fromDocument(document);
|
|
|
|
expect(document.keys.toSet(), ProjectStatisticsDocumentFields.all);
|
|
expect(document[ProjectStatisticsDocumentFields.id], 'home');
|
|
expect(document[ProjectStatisticsDocumentFields.projectId], 'home');
|
|
expect(document[ProjectStatisticsDocumentFields.durationMinuteCounts], {
|
|
'20': 3,
|
|
'45': 5,
|
|
});
|
|
expect(document[ProjectStatisticsDocumentFields.rewardCounts], {
|
|
'high': 5,
|
|
'low': 3,
|
|
});
|
|
expect(document[ProjectStatisticsDocumentFields.appliedActivityIds], [
|
|
'activity-1',
|
|
]);
|
|
expect(restored.projectId, statistics.projectId);
|
|
expect(restored.completedTaskCount, 8);
|
|
expect(restored.durationMinuteCounts, statistics.durationMinuteCounts);
|
|
expect(restored.completionTimeBucketCounts,
|
|
statistics.completionTimeBucketCounts);
|
|
expect(restored.reminderProfileCounts, statistics.reminderProfileCounts);
|
|
});
|
|
|
|
test(
|
|
'locked block and override documents preserve civil dates and wall time',
|
|
() {
|
|
final recurrence = LockedBlockRecurrence.weekly(
|
|
weekdays: {LockedWeekday.monday, LockedWeekday.thursday},
|
|
);
|
|
final recurringBlock = LockedBlock(
|
|
id: 'work',
|
|
name: 'Work',
|
|
startTime: WallTime(hour: 9, minute: 0),
|
|
endTime: WallTime(hour: 17, minute: 0),
|
|
recurrence: recurrence,
|
|
hiddenByDefault: true,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
archivedAt: DateTime.utc(2026, 6, 30),
|
|
);
|
|
final replaceOverride = LockedBlockOverride.replace(
|
|
id: 'replace-work',
|
|
lockedBlockId: 'work',
|
|
date: CivilDate(2026, 6, 25),
|
|
startTime: WallTime(hour: 10, minute: 0),
|
|
endTime: WallTime(hour: 15, minute: 0),
|
|
createdAt: createdAt,
|
|
name: 'Short work',
|
|
updatedAt: updatedAt,
|
|
);
|
|
final addOverride = LockedBlockOverride.add(
|
|
id: 'appointment',
|
|
date: CivilDate(2026, 6, 25),
|
|
name: 'Appointment',
|
|
startTime: WallTime(hour: 18, minute: 0),
|
|
endTime: WallTime(hour: 19, minute: 0),
|
|
createdAt: createdAt,
|
|
);
|
|
final removeOverride = LockedBlockOverride.remove(
|
|
id: 'remove-work',
|
|
lockedBlockId: 'work',
|
|
date: CivilDate(2026, 6, 26),
|
|
createdAt: createdAt,
|
|
);
|
|
|
|
final blockDocument = LockedBlockDocumentMapping.toDocument(
|
|
recurringBlock,
|
|
ownerId: 'owner-1',
|
|
);
|
|
final restoredBlock = LockedBlockDocumentMapping.fromDocument(
|
|
blockDocument,
|
|
);
|
|
final replaceDocument = LockedBlockOverrideDocumentMapping.toDocument(
|
|
replaceOverride,
|
|
ownerId: 'owner-1',
|
|
);
|
|
final addDocument = LockedBlockOverrideDocumentMapping.toDocument(
|
|
addOverride,
|
|
ownerId: 'owner-1',
|
|
);
|
|
final removeDocument = LockedBlockOverrideDocumentMapping.toDocument(
|
|
removeOverride,
|
|
ownerId: 'owner-1',
|
|
);
|
|
|
|
expect(blockDocument[LockedBlockDocumentFields.startTime], '09:00');
|
|
expect(
|
|
(blockDocument[LockedBlockDocumentFields.recurrence] as Map<String,
|
|
Object?>)[LockedBlockRecurrenceDocumentFields.weekdays],
|
|
['monday', 'thursday'],
|
|
);
|
|
expect(restoredBlock.recurrence!.weekdays, recurrence.weekdays);
|
|
expect(restoredBlock.archivedAt, recurringBlock.archivedAt);
|
|
expect(replaceDocument[LockedBlockOverrideDocumentFields.date],
|
|
'2026-06-25');
|
|
expect(
|
|
replaceDocument[LockedBlockOverrideDocumentFields.type], 'replace');
|
|
expect(addDocument[LockedBlockOverrideDocumentFields.type], 'add');
|
|
expect(removeDocument[LockedBlockOverrideDocumentFields.type], 'remove');
|
|
expect(
|
|
LockedBlockOverrideDocumentMapping.fromDocument(replaceDocument).date,
|
|
CivilDate(2026, 6, 25),
|
|
);
|
|
expect(
|
|
LockedBlockOverrideDocumentMapping.fromDocument(removeDocument)
|
|
.startTime,
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('settings, notices, operations, and activities round-trip', () {
|
|
final settings = OwnerSettings(
|
|
ownerId: 'owner-1',
|
|
timeZoneId: 'America/Los_Angeles',
|
|
dayStart: WallTime(hour: 7, minute: 30),
|
|
dayEnd: WallTime(hour: 21, minute: 0),
|
|
compactModeEnabled: true,
|
|
backlogStalenessSettings: const BacklogStalenessSettings(
|
|
greenMaxAge: Duration(days: 3),
|
|
blueMaxAge: Duration(days: 10),
|
|
),
|
|
);
|
|
final activity = TaskActivity(
|
|
id: 'activity-1',
|
|
operationId: 'op-1',
|
|
code: TaskActivityCode.completed,
|
|
taskId: 'task-1',
|
|
projectId: 'home',
|
|
occurredAt: createdAt,
|
|
metadata: {'completedAt': createdAt},
|
|
);
|
|
final acknowledgement = NoticeAcknowledgementRecord(
|
|
noticeId: 'snapshot:0',
|
|
ownerId: 'owner-1',
|
|
acknowledgedAt: updatedAt,
|
|
);
|
|
final operation = ApplicationOperationRecord(
|
|
operationId: 'op-1',
|
|
ownerId: 'owner-1',
|
|
operationName: 'completeTask',
|
|
committedAt: updatedAt,
|
|
);
|
|
|
|
final settingsDocument = OwnerSettingsDocumentMapping.toDocument(
|
|
settings,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
);
|
|
final activityDocument = TaskActivityDocumentMapping.toDocument(
|
|
activity,
|
|
ownerId: 'owner-1',
|
|
);
|
|
final acknowledgementDocument =
|
|
NoticeAcknowledgementDocumentMapping.toDocument(acknowledgement);
|
|
final operationDocument = ApplicationOperationDocumentMapping.toDocument(
|
|
operation,
|
|
);
|
|
|
|
expect(settingsDocument[OwnerSettingsDocumentFields.dayStart], '07:30');
|
|
expect(
|
|
(settingsDocument[OwnerSettingsDocumentFields.backlogStaleness] as Map<
|
|
String, Object?>)[BacklogStalenessDocumentFields.blueMaxAgeDays],
|
|
10,
|
|
);
|
|
expect(activityDocument[TaskActivityDocumentFields.code], 'completed');
|
|
expect(
|
|
(activityDocument[TaskActivityDocumentFields.metadata]
|
|
as Map<String, Object?>)['completedAt'],
|
|
'2026-06-23T08:00:00.000Z',
|
|
);
|
|
expect(acknowledgementDocument[NoticeAcknowledgementDocumentFields.id],
|
|
'snapshot:0');
|
|
expect(operationDocument[ApplicationOperationDocumentFields.operationId],
|
|
'op-1');
|
|
|
|
expect(OwnerSettingsDocumentMapping.fromDocument(settingsDocument).dayEnd,
|
|
settings.dayEnd);
|
|
expect(TaskActivityDocumentMapping.fromDocument(activityDocument).code,
|
|
TaskActivityCode.completed);
|
|
expect(
|
|
NoticeAcknowledgementDocumentMapping.fromDocument(
|
|
acknowledgementDocument,
|
|
).noticeId,
|
|
'snapshot:0',
|
|
);
|
|
expect(
|
|
ApplicationOperationDocumentMapping.fromDocument(operationDocument)
|
|
.operationName,
|
|
'completeTask',
|
|
);
|
|
});
|
|
|
|
test('scheduling snapshot structures round-trip nested data', () {
|
|
final task = scheduledTask(id: 'task-1', createdAt: createdAt);
|
|
final snapshot = SchedulingStateSnapshot(
|
|
id: 'rollover:owner-1:2026-06-25',
|
|
capturedAt: updatedAt,
|
|
window: SchedulingWindow(
|
|
start: DateTime.utc(2026, 6, 24, 9),
|
|
end: DateTime.utc(2026, 6, 24, 17),
|
|
),
|
|
tasks: [task],
|
|
lockedIntervals: [
|
|
TimeInterval(
|
|
start: DateTime.utc(2026, 6, 24, 12),
|
|
end: DateTime.utc(2026, 6, 24, 13),
|
|
label: 'Hidden locked',
|
|
),
|
|
],
|
|
notices: [
|
|
SchedulingNotice(
|
|
'1 unfinished flexible task was moved to today.',
|
|
type: SchedulingNoticeType.moved,
|
|
movementCode:
|
|
SchedulingMovementCode.unfinishedFlexibleTasksRolledOver,
|
|
parameters: {'count': 1},
|
|
),
|
|
],
|
|
changes: [
|
|
SchedulingChange(
|
|
taskId: task.id,
|
|
previousStart: task.scheduledStart,
|
|
previousEnd: task.scheduledEnd,
|
|
nextStart: DateTime.utc(2026, 6, 24, 9),
|
|
nextEnd: DateTime.utc(2026, 6, 24, 9, 30),
|
|
),
|
|
],
|
|
overlaps: [
|
|
SchedulingOverlap(
|
|
taskId: task.id,
|
|
taskInterval: TimeInterval(
|
|
start: DateTime.utc(2026, 6, 23, 9),
|
|
end: DateTime.utc(2026, 6, 23, 9, 30),
|
|
),
|
|
blockedInterval: TimeInterval(
|
|
start: DateTime.utc(2026, 6, 23, 9, 15),
|
|
end: DateTime.utc(2026, 6, 23, 9, 45),
|
|
),
|
|
),
|
|
],
|
|
operationName: 'appOpenRecovery',
|
|
);
|
|
|
|
final document = SchedulingSnapshotDocumentMapping.toDocument(
|
|
snapshot,
|
|
ownerId: 'owner-1',
|
|
sourceDate: CivilDate(2026, 6, 23),
|
|
targetDate: CivilDate(2026, 6, 24),
|
|
retentionExpiresAt: DateTime.utc(2026, 7, 24),
|
|
);
|
|
final restored = SchedulingSnapshotDocumentMapping.fromDocument(document);
|
|
|
|
expect(document.keys.toSet(), SchedulingSnapshotDocumentFields.all);
|
|
expect(
|
|
document[SchedulingSnapshotDocumentFields.sourceDate], '2026-06-23');
|
|
expect(restored.id, snapshot.id);
|
|
expect(restored.window.start, snapshot.window.start);
|
|
expect(restored.tasks.single.id, task.id);
|
|
expect(restored.lockedIntervals.single.label, 'Hidden locked');
|
|
expect(restored.notices.single.movementCode,
|
|
SchedulingMovementCode.unfinishedFlexibleTasksRolledOver);
|
|
expect(restored.notices.single.parameters['count'], 1);
|
|
expect(restored.changes.single.taskId, task.id);
|
|
expect(restored.overlaps.single.taskId, task.id);
|
|
});
|
|
|
|
test('stable enum code tables do not depend on Dart enum names', () {
|
|
expect(
|
|
PersistenceEnumMapping.encodeReward(RewardLevel.notSet), 'not_set');
|
|
expect(
|
|
PersistenceEnumMapping.encodeTaskStatus(
|
|
TaskStatus.noLongerRelevant,
|
|
),
|
|
'no_longer_relevant');
|
|
expect(PersistenceEnumMapping.encodeTaskType(TaskType.freeSlot),
|
|
'free_slot');
|
|
expect(
|
|
PersistenceEnumMapping.decodeTaskStatus('no_longer_relevant'),
|
|
TaskStatus.noLongerRelevant,
|
|
);
|
|
expect(
|
|
PersistenceEnumMapping.decodeReward('not_set'),
|
|
RewardLevel.notSet,
|
|
);
|
|
});
|
|
|
|
test('malformed documents produce typed mapping failures', () {
|
|
final valid = scheduledTask(id: 'task-1', createdAt: createdAt)
|
|
.toDocument(ownerId: 'owner-1');
|
|
|
|
expect(
|
|
() => TaskDocumentMapping.fromDocument(
|
|
{...valid}..remove(TaskDocumentFields.title),
|
|
),
|
|
throwsMapping(
|
|
DocumentMappingFailureCode.missingField, TaskDocumentFields.title),
|
|
);
|
|
expect(
|
|
() => TaskDocumentMapping.fromDocument({
|
|
...valid,
|
|
TaskDocumentFields.reward: 'notSet',
|
|
}),
|
|
throwsMapping(DocumentMappingFailureCode.unknownCode, 'RewardLevel'),
|
|
);
|
|
expect(
|
|
() => TaskDocumentMapping.fromDocument({
|
|
...valid,
|
|
TaskDocumentFields.durationMinutes: '30',
|
|
}),
|
|
throwsMapping(DocumentMappingFailureCode.wrongType,
|
|
TaskDocumentFields.durationMinutes),
|
|
);
|
|
expect(
|
|
() => TaskDocumentMapping.fromDocument({
|
|
...valid,
|
|
DocumentFields.revision: 0,
|
|
}),
|
|
throwsMapping(DocumentMappingFailureCode.invalidRevision,
|
|
DocumentFields.revision),
|
|
);
|
|
expect(
|
|
() => TaskDocumentMapping.fromDocument({
|
|
...valid,
|
|
DocumentFields.schemaVersion: 99,
|
|
}),
|
|
throwsMapping(DocumentMappingFailureCode.invalidSchemaVersion,
|
|
DocumentFields.schemaVersion),
|
|
);
|
|
expect(
|
|
() => SchedulingWindowDocumentMapping.fromDocument({
|
|
SchedulingWindowDocumentFields.start: '2026-06-23T10:00:00.000Z',
|
|
SchedulingWindowDocumentFields.end: '2026-06-23T09:00:00.000Z',
|
|
}),
|
|
throwsMapping(DocumentMappingFailureCode.invalidValue),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
Matcher throwsMapping(
|
|
DocumentMappingFailureCode code, [
|
|
String? fieldName,
|
|
]) {
|
|
var matcher = isA<DocumentMappingException>().having(
|
|
(error) => error.code,
|
|
'code',
|
|
code,
|
|
);
|
|
if (fieldName != null) {
|
|
matcher = matcher.having(
|
|
(error) => error.fieldName,
|
|
'fieldName',
|
|
fieldName,
|
|
);
|
|
}
|
|
return throwsA(matcher);
|
|
}
|
|
|
|
Task scheduledTask({
|
|
required String id,
|
|
required DateTime createdAt,
|
|
RewardLevel reward = RewardLevel.notSet,
|
|
DifficultyLevel difficulty = DifficultyLevel.medium,
|
|
}) {
|
|
return Task(
|
|
id: id,
|
|
title: 'Scheduled task',
|
|
projectId: 'project-1',
|
|
type: TaskType.flexible,
|
|
status: TaskStatus.planned,
|
|
priority: PriorityLevel.medium,
|
|
reward: reward,
|
|
difficulty: difficulty,
|
|
durationMinutes: 30,
|
|
scheduledStart: DateTime.utc(2026, 6, 23, 9),
|
|
scheduledEnd: DateTime.utc(2026, 6, 23, 9, 30),
|
|
actualStart: DateTime.utc(2026, 6, 23, 9, 5),
|
|
actualEnd: DateTime.utc(2026, 6, 23, 9, 25),
|
|
completedAt: DateTime.utc(2026, 6, 23, 9, 30),
|
|
parentTaskId: 'parent-1',
|
|
backlogTags: const {BacklogTag.wishlist},
|
|
reminderOverride: ReminderProfile.persistent,
|
|
createdAt: createdAt,
|
|
updatedAt: createdAt.add(const Duration(minutes: 5)),
|
|
);
|
|
}
|