quorlasocial/connector/test/competitors.test.mjs
metatroncubeswdev 8b6e1cf835 Competitors page: track Instagram competitors per client
- Add/remove competitor handles (public Business/Creator accounts); refreshed nightly, one snapshot a day
- Compare with the client: followers, 30-day change, trend, posts/week, typical post (median), engagement rate, format mix
- Engagement-rate chart and each competitor's top recent posts
- MCP tools competitors and competitor_add (audits can add the AI's suggestions)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:22:33 -04:00

34 lines
1.9 KiB
JavaScript

// 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/);
});