Two operational scripts and their shared library. Both are read-only by default; the pruner requires an explicit --apply flag to touch anything. scripts/webex-rooms-report.ts - Fetches every room the current WEBEX_TOKEN can see (paginated /v1/rooms) - Buckets rooms by lastActivity age (7d, 30d, 3m, 6m, 12m, 18m, 24m, >24m) - Cross-references against legacy/spaces.json to distinguish sendi-managed rooms from others - Prints a summary table plus a sample listing; makes zero writes scripts/prune-stale-spaces.ts - Finds Space rows whose most recent Message.createdAt (falling back to Space.createdAt) is older than --months=N (default 24) - Dry-run by default; --apply performs deletions - Deletes the Webex room via REST (Bearer WEBEX_TOKEN) then removes the Space row (cascade removes Message, BulkJob, Survey, PendingMessage) - --no-webex skips the Webex delete for DB-only pruning - --limit=N caps how many spaces get processed per run src/lib/staleSpaces.ts - Pure `monthsAgo` and `computeLastActivity` helpers used by the pruner - `findStaleSpaces(prisma, months)` runs the query and sorts oldest-first package.json scripts: `webex-rooms-report`, `prune-spaces` Tests: 6 new unit tests for the pure helpers (51 total, all green) Co-authored-by: Cursor <cursoragent@cursor.com>
39 lines
1.5 KiB
TypeScript
39 lines
1.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { monthsAgo, computeLastActivity } from '../../src/lib/staleSpaces.ts';
|
|
|
|
describe('monthsAgo', () => {
|
|
it('subtracts calendar months', () => {
|
|
const now = new Date('2026-07-06T12:00:00Z');
|
|
const back = monthsAgo(24, now);
|
|
expect(back.getUTCFullYear()).toBe(2024);
|
|
expect(back.getUTCMonth()).toBe(6); // July (0-indexed)
|
|
});
|
|
|
|
it('handles month-boundary edge without throwing (Mar 31 → Feb 28/Mar 3)', () => {
|
|
const now = new Date('2026-03-31T00:00:00Z');
|
|
expect(() => monthsAgo(1, now)).not.toThrow();
|
|
});
|
|
|
|
it('is idempotent for months=0', () => {
|
|
const now = new Date('2026-01-15T00:00:00Z');
|
|
expect(monthsAgo(0, now).toISOString()).toBe(now.toISOString());
|
|
});
|
|
});
|
|
|
|
describe('computeLastActivity', () => {
|
|
const older = new Date('2024-01-01T00:00:00Z');
|
|
const newer = new Date('2025-06-01T00:00:00Z');
|
|
|
|
it('returns spaceCreatedAt when there are no messages', () => {
|
|
expect(computeLastActivity(older, null).toISOString()).toBe(older.toISOString());
|
|
expect(computeLastActivity(older, undefined).toISOString()).toBe(older.toISOString());
|
|
});
|
|
|
|
it('returns the message date when it is newer', () => {
|
|
expect(computeLastActivity(older, newer).toISOString()).toBe(newer.toISOString());
|
|
});
|
|
|
|
it('returns spaceCreatedAt when the message is older (defensive)', () => {
|
|
expect(computeLastActivity(newer, older).toISOString()).toBe(newer.toISOString());
|
|
});
|
|
});
|