ktg-plugin-marketplace/plugins/linkedin-thought-leadership/scripts/analytics/tests/alerts.test.ts
Kjell Tore Guttormsen 39f8b275a6 feat(linkedin-thought-leadership): v1.0.0 — initial open-source import
Build LinkedIn thought leadership with algorithmic understanding,
strategic consistency, and AI-assisted content creation. Updated for
the January 2026 360Brew algorithm change.

16 agents, 25 commands, 6 skills, 9 hooks, 24 reference docs.

Personal data sanitized: voice samples generalized to template,
high-engagement posts cleared, region-specific references replaced
with placeholders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 22:09:03 +02:00

205 lines
9.3 KiB
TypeScript

import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { detectAlerts, detectWeeklyAlerts } from "../src/utils/alerts.js";
import type { PostAnalytics } from "../src/models/types.js";
/**
* Helper function to create PostAnalytics with default values.
*/
function makePost(overrides: Partial<PostAnalytics> = {}): PostAnalytics {
return {
id: `post-${Math.random().toString(36).substr(2, 9)}`,
title: "Test Post",
publishedDate: "2026-01-15",
metrics: {
impressions: 1000,
reactions: 50,
comments: 10,
shares: 5,
clicks: 100,
engagementRate: 5.0,
},
importedAt: new Date().toISOString(),
exportSource: "LinkedIn",
...overrides,
};
}
describe("alerts", () => {
describe("detectAlerts", () => {
test("should find spike posts", () => {
// Create posts with one outlier high value
// Need value that's > 2.0 standard deviations from mean (not >=)
// Using more base values to create a scenario where outlier exceeds threshold
const posts = [
makePost({ id: "1", title: "Normal Post 1", metrics: { impressions: 1000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "2", title: "Normal Post 2", metrics: { impressions: 1200, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "3", title: "Normal Post 3", metrics: { impressions: 1100, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "4", title: "Normal Post 4", metrics: { impressions: 900, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "5", title: "Normal Post 5", metrics: { impressions: 1050, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "6", title: "Viral Post", metrics: { impressions: 10000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
];
const alerts = detectAlerts(posts, "impressions");
assert.ok(alerts.length > 0, "Should detect at least one alert");
const spikeAlert = alerts.find(a => a.type === "spike");
assert.ok(spikeAlert, "Should have a spike alert");
assert.equal(spikeAlert.severity, "info");
assert.equal(spikeAlert.postId, "6");
assert.ok(spikeAlert.message.includes("Viral Post"));
});
test("should find drop posts", () => {
// Create posts with one outlier low value
const posts = [
makePost({ id: "1", title: "Normal Post 1", metrics: { impressions: 10000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "2", title: "Normal Post 2", metrics: { impressions: 10000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "3", title: "Normal Post 3", metrics: { impressions: 10000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "4", title: "Low Reach Post", metrics: { impressions: 100, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
];
const alerts = detectAlerts(posts, "impressions");
assert.equal(alerts.length, 1);
assert.equal(alerts[0].type, "drop");
assert.equal(alerts[0].severity, "warning");
assert.equal(alerts[0].postId, "4");
assert.ok(alerts[0].message.includes("Low Reach Post"));
});
test("should return empty for uniform data", () => {
const posts = [
makePost({ id: "1", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "2", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "3", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
];
const alerts = detectAlerts(posts, "impressions");
assert.equal(alerts.length, 0);
});
test("should handle empty posts array", () => {
const alerts = detectAlerts([]);
assert.equal(alerts.length, 0);
});
test("should sort alerts by severity", () => {
// Create scenario with multiple alerts of different severities
// For this, we'd need to manually create alerts with different severities
// Since detectAlerts only produces "info" spikes and "warning" drops,
// let's just verify the sorting works with what we have
const posts = [
makePost({ id: "1", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "2", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "3", metrics: { impressions: 100, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }), // Drop
makePost({ id: "4", metrics: { impressions: 50000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }), // Spike
];
const alerts = detectAlerts(posts, "impressions");
// Should have drop (warning) first, then spike (info)
if (alerts.length > 1) {
assert.equal(alerts[0].severity, "warning");
assert.equal(alerts[1].severity, "info");
}
});
});
describe("detectWeeklyAlerts", () => {
test("should detect critical drop in impressions", () => {
const current = { impressions: 1000, engagementRate: 5.0 };
const previous = { impressions: 3000, engagementRate: 5.0 }; // -66.7% drop
const alerts = detectWeeklyAlerts(current, previous);
const impressionAlerts = alerts.filter((a) => a.metric === "impressions");
assert.ok(impressionAlerts.length > 0);
assert.equal(impressionAlerts[0].severity, "critical");
assert.equal(impressionAlerts[0].type, "drop");
});
test("should detect warning drop in impressions", () => {
const current = { impressions: 6000, engagementRate: 5.0 };
const previous = { impressions: 10000, engagementRate: 5.0 }; // -40% drop
const alerts = detectWeeklyAlerts(current, previous);
const impressionAlerts = alerts.filter((a) => a.metric === "impressions");
assert.ok(impressionAlerts.length > 0);
assert.equal(impressionAlerts[0].severity, "warning");
assert.equal(impressionAlerts[0].type, "drop");
});
test("should detect spike in impressions", () => {
const current = { impressions: 25000, engagementRate: 5.0 };
const previous = { impressions: 10000, engagementRate: 5.0 }; // +150% increase
const alerts = detectWeeklyAlerts(current, previous);
const impressionAlerts = alerts.filter((a) => a.metric === "impressions");
assert.ok(impressionAlerts.length > 0);
assert.equal(impressionAlerts[0].severity, "info");
assert.equal(impressionAlerts[0].type, "spike");
});
test("should detect critical drop in engagement rate", () => {
const current = { impressions: 10000, engagementRate: 2.0 };
const previous = { impressions: 10000, engagementRate: 6.0 }; // -66.7% drop
const alerts = detectWeeklyAlerts(current, previous);
const engagementAlerts = alerts.filter((a) => a.metric === "engagementRate");
assert.ok(engagementAlerts.length > 0);
assert.equal(engagementAlerts[0].severity, "critical");
assert.equal(engagementAlerts[0].type, "drop");
});
test("should detect warning drop in engagement rate", () => {
const current = { impressions: 10000, engagementRate: 3.0 };
const previous = { impressions: 10000, engagementRate: 5.0 }; // -40% drop
const alerts = detectWeeklyAlerts(current, previous);
const engagementAlerts = alerts.filter((a) => a.metric === "engagementRate");
assert.ok(engagementAlerts.length > 0);
assert.equal(engagementAlerts[0].severity, "warning");
assert.equal(engagementAlerts[0].type, "drop");
});
test("should detect spike in engagement rate", () => {
const current = { impressions: 10000, engagementRate: 12.0 };
const previous = { impressions: 10000, engagementRate: 5.0 }; // +140% increase
const alerts = detectWeeklyAlerts(current, previous);
const engagementAlerts = alerts.filter((a) => a.metric === "engagementRate");
assert.ok(engagementAlerts.length > 0);
assert.equal(engagementAlerts[0].severity, "info");
assert.equal(engagementAlerts[0].type, "spike");
});
test("should return empty for stable metrics", () => {
const current = { impressions: 10000, engagementRate: 5.0 };
const previous = { impressions: 10200, engagementRate: 5.1 }; // Small changes
const alerts = detectWeeklyAlerts(current, previous);
assert.equal(alerts.length, 0);
});
test("should handle multiple alerts and sort by severity", () => {
const current = { impressions: 1000, engagementRate: 2.0 };
const previous = { impressions: 3000, engagementRate: 6.0 }; // Both critical drops
const alerts = detectWeeklyAlerts(current, previous);
assert.ok(alerts.length >= 2);
// All should be critical
alerts.forEach((alert) => {
assert.equal(alert.severity, "critical");
});
});
});
});