import { describe, test, afterEach } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { generateMonthlyReport } from "../src/reports/monthly.js"; import { saveBatch } from "../src/utils/storage.js"; import type { PostAnalytics, AnalyticsBatch, MonthlyReport } from "../src/models/types.js"; function createPost(date: string, impressions: number, engagementRate: number): PostAnalytics { return { id: `post-${date}-${impressions}`, title: `Post on ${date}`, publishedDate: date, metrics: { impressions, reactions: Math.round(impressions * 0.05), comments: Math.round(impressions * 0.01), shares: Math.round(impressions * 0.005), clicks: Math.round(impressions * 0.02), engagementRate, }, importedAt: "2026-04-01T00:00:00Z", exportSource: "test.csv", }; } function createBatch(posts: PostAnalytics[]): AnalyticsBatch { const dates = posts.map(p => p.publishedDate).sort(); return { batchId: "test-batch-" + Math.random().toString(36).slice(2, 10), importedAt: "2026-04-01T00:00:00Z", exportFilename: "test.csv", dateRange: { from: dates[0], to: dates[dates.length - 1] }, postCount: posts.length, posts, }; } let tmpDir: string; function setupTestRoot(posts: PostAnalytics[]): string { tmpDir = mkdtempSync(join(tmpdir(), "monthly-test-")); for (const dir of ["exports", "posts", "weekly-reports", "monthly-reports"]) { mkdirSync(join(tmpDir, dir), { recursive: true }); } if (posts.length > 0) { saveBatch(tmpDir, createBatch(posts)); } return tmpDir; } afterEach(() => { if (tmpDir) rmSync(tmpDir, { recursive: true, force: true }); }); describe("generateMonthlyReport", () => { const marchPosts: PostAnalytics[] = [ createPost("2026-03-03", 1000, 3.0), createPost("2026-03-05", 2000, 4.0), createPost("2026-03-10", 1500, 3.5), createPost("2026-03-17", 3000, 5.0), createPost("2026-03-24", 2500, 4.5), ]; const febPosts: PostAnalytics[] = [ createPost("2026-02-03", 800, 2.5), createPost("2026-02-10", 1200, 3.0), createPost("2026-02-17", 900, 2.8), ]; test("filters posts to correct month", () => { const root = setupTestRoot([...marchPosts, ...febPosts]); const report = generateMonthlyReport(root, "2026-03"); assert.equal(report.summary.totalPosts, 5); }); test("calculates correct monthly totals", () => { const root = setupTestRoot(marchPosts); const report = generateMonthlyReport(root, "2026-03"); assert.equal(report.summary.totalImpressions, 10000); // 1000+2000+1500+3000+2500 assert.equal(report.summary.totalPosts, 5); assert.equal(report.summary.avgImpressionsPerPost, 2000); }); test("generates weekly breakdown within month", () => { const root = setupTestRoot(marchPosts); const report = generateMonthlyReport(root, "2026-03"); assert.ok(report.byWeek.length > 0); const totalPostsInWeeks = report.byWeek.reduce((sum, w) => sum + w.postCount, 0); assert.equal(totalPostsInWeeks, 5); }); test("calculates MoM deltas when previous month exists", () => { const root = setupTestRoot([...febPosts, ...marchPosts]); // First generate February report so it exists for comparison generateMonthlyReport(root, "2026-02"); const report = generateMonthlyReport(root, "2026-03"); assert.notEqual(report.trends.comparedTo, null); assert.equal(report.trends.comparedTo, "2026-02"); assert.notEqual(report.trends.percentChange.impressions, null); assert.notEqual(report.trends.percentChange.postCount, null); }); test("handles no previous month data", () => { const root = setupTestRoot(marchPosts); const report = generateMonthlyReport(root, "2026-03"); assert.equal(report.trends.comparedTo, null); assert.equal(report.trends.percentChange.impressions, null); assert.equal(report.trends.percentChange.engagement, null); }); test("handles month with no posts", () => { const root = setupTestRoot(marchPosts); const report = generateMonthlyReport(root, "2026-01"); assert.equal(report.summary.totalPosts, 0); assert.equal(report.summary.totalImpressions, 0); assert.equal(report.summary.avgImpressionsPerPost, 0); assert.equal(report.byWeek.length, 0); }); test("identifies top performers", () => { const root = setupTestRoot(marchPosts); const report = generateMonthlyReport(root, "2026-03"); assert.ok(report.topPerformers.length > 0); assert.equal(report.topPerformers[0].metrics.impressions, 3000); }); test("sets correct month field", () => { const root = setupTestRoot(marchPosts); const report = generateMonthlyReport(root, "2026-03"); assert.equal(report.month, "2026-03"); }); });