diff --git a/src/__tests__/db/schedule.test.ts b/src/__tests__/db/schedule.test.ts new file mode 100644 index 0000000..31dddeb --- /dev/null +++ b/src/__tests__/db/schedule.test.ts @@ -0,0 +1,51 @@ +import { getDb, resetDb } from '../../db'; +import { + insertScheduleEntry, getScheduleByDate, updateScheduleStatus, + getUpcomingSchedule, getPendingDates, +} from '../../db/schedule'; + +beforeEach(() => { resetDb(); }); + +describe('schedule', () => { + it('inserts and retrieves a schedule entry', () => { + const db = getDb(); + insertScheduleEntry(db, '2026-05-22', 60, 'Continuing current level'); + const e = getScheduleByDate(db, '2026-05-22'); + expect(e).not.toBeNull(); + expect(e!.planned_min).toBe(60); + expect(e!.status).toBe('pending'); + }); + + it('updates status to completed', () => { + const db = getDb(); + insertScheduleEntry(db, '2026-05-22', 60, null); + updateScheduleStatus(db, '2026-05-22', 'completed', null); + expect(getScheduleByDate(db, '2026-05-22')!.status).toBe('completed'); + }); + + it('updates status to skipped with reason', () => { + const db = getDb(); + insertScheduleEntry(db, '2026-05-22', 60, null); + updateScheduleStatus(db, '2026-05-22', 'skipped', 'work trip'); + const e = getScheduleByDate(db, '2026-05-22')!; + expect(e.status).toBe('skipped'); + expect(e.skip_reason).toBe('work trip'); + }); + + it('returns upcoming entries from today onwards ordered by date', () => { + const db = getDb(); + insertScheduleEntry(db, '2026-05-21', 60, null); + insertScheduleEntry(db, '2026-05-23', 75, null); + const rows = getUpcomingSchedule(db, '2026-05-21'); + expect(rows).toHaveLength(2); + expect(rows[0]!.date).toBe('2026-05-21'); + }); + + it('returns only pending dates', () => { + const db = getDb(); + insertScheduleEntry(db, '2026-05-21', 60, null); + insertScheduleEntry(db, '2026-05-22', 60, null); + updateScheduleStatus(db, '2026-05-21', 'completed', null); + expect(getPendingDates(db)).toEqual(['2026-05-22']); + }); +}); diff --git a/src/db/schedule.ts b/src/db/schedule.ts new file mode 100644 index 0000000..503bcec --- /dev/null +++ b/src/db/schedule.ts @@ -0,0 +1,47 @@ +import type { Database } from 'better-sqlite3'; + +export type ScheduleStatus = 'pending' | 'completed' | 'skipped'; + +export interface ScheduleEntry { + id: number; + date: string; + planned_min: number; + status: ScheduleStatus; + skip_reason: string | null; + ai_rationale: string | null; + created_at: string; +} + +export function insertScheduleEntry( + db: Database, date: string, planned_min: number, ai_rationale: string | null +): ScheduleEntry { + db.prepare( + 'INSERT OR IGNORE INTO schedule (date, planned_min, ai_rationale) VALUES (?, ?, ?)' + ).run(date, planned_min, ai_rationale); + return getScheduleByDate(db, date)!; +} + +export function getScheduleByDate(db: Database, date: string): ScheduleEntry | null { + return (db.prepare('SELECT * FROM schedule WHERE date = ?').get(date) as ScheduleEntry | undefined) ?? null; +} + +export function updateScheduleStatus( + db: Database, date: string, status: ScheduleStatus, skip_reason: string | null +): void { + db.prepare( + 'UPDATE schedule SET status = ?, skip_reason = ? WHERE date = ?' + ).run(status, skip_reason, date); +} + +export function getUpcomingSchedule(db: Database, fromDate: string): ScheduleEntry[] { + return db.prepare( + 'SELECT * FROM schedule WHERE date >= ? ORDER BY date ASC' + ).all(fromDate) as ScheduleEntry[]; +} + +export function getPendingDates(db: Database): string[] { + const rows = db.prepare( + "SELECT date FROM schedule WHERE status = 'pending' ORDER BY date ASC" + ).all() as Array<{ date: string }>; + return rows.map(r => r.date); +}