// Competitor comparison maths, offline (no Instagram calls): median engagement, rate, growth, format mix, top posts. import { test } from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "quorla-comp-")); process.env.QUORLA_COMPETITORS_FILE = path.join(TMP, "competitors.json"); const { PROJECT_ROOT } = await import("../src/env.js"); const client = fs.readdirSync(path.join(PROJECT_ROOT, "clients")).find((d) => !d.startsWith("_") && fs.existsSync(path.join(PROJECT_ROOT, "clients", d, "client.json"))); const day = (n) => new Date(Date.now() - n * 86400000); const media = [5, 10, 20, 40, 1000].map((likes, i) => ({ id: `m${i}`, at: day(i * 3).toISOString(), likes, comments: 0, type: i % 2 ? "Reel" : "Image", permalink: `https://x/${i}` })); test("compare: median engagement ignores one viral post; growth from history", { skip: !client && "no client folder" }, async () => { fs.writeFileSync(process.env.QUORLA_COMPETITORS_FILE, JSON.stringify({ [client]: { accounts: [{ username: "rival", name: "Rival", source: "manual", snapshots: [ { date: day(20).toISOString().slice(0, 10), followers: 900 }, { date: day(0).toISOString().slice(0, 10), followers: 1000, media }, ] }] }, })); const c = await import("../src/competitors.js"); const r = c.compare(client, { username: "me", followers: 500, posts: [], history: [] }).competitors[0]; assert.equal(r.avgEngagement, 20, "median of 5,10,20,40,1000"); assert.equal(r.engagementRate, 2); assert.equal(r.growth30, 100); assert.equal(r.postsPerWeek, 1.3, "5 posts in 28 days"); assert.deepEqual(r.formatMix, { Image: 3, Reel: 2 }); assert.equal(r.topPosts[0].likes, 1000); assert.equal(c.listTracked(client)[0].username, "rival"); await assert.rejects(c.add(client, "not a handle!!"), /Instagram handle/); });