sc-postiz-poster/test/e2e.js
2026-06-17 17:40:57 -05:00

162 lines
6 KiB
JavaScript

// postiz-poster integration test suite.
//
// Two gates:
// 1. payloadGate -- pure assertions on buildPostPayload (always runs, no net).
// 2. liveGate -- drives a real Postiz instance; runs only when both
// POSTIZ_BASE_URL and POSTIZ_API_KEY are set.
//
// Run from the postiz/ directory:
//
// node test/e2e.js # payloadGate only
// POSTIZ_BASE_URL=http://localhost:5000 \
// POSTIZ_API_KEY=xxxxxxxx node test/e2e.js # + liveGate
//
// Without the env vars the live gate is skipped (not failed), so the pure gate
// still runs anywhere with no Postiz available. Assertion failures are caught
// and counted; one failure does not stop the suite.
const assert = require("node:assert/strict");
const { buildPostPayload } = require("../lib/action");
const { makePostizClient } = require("../lib/postizClient");
const { encryptSecret, decryptSecret, isEncryptedBlob } = require("../lib/secretsAtRest");
const { POST_TYPES } = require("../lib/constants");
let passed = 0;
let failed = 0;
const check = (name, fn) => {
try {
fn();
passed += 1;
console.log(`ok - ${name}`);
} catch (err) {
failed += 1;
console.log(`FAIL - ${name}: ${err && err.message ? err.message : err}`);
}
};
const checkAsync = async (name, fn) => {
try {
await fn();
passed += 1;
console.log(`ok - ${name}`);
} catch (err) {
failed += 1;
console.log(`FAIL - ${name}: ${err && err.message ? err.message : err}`);
}
};
const payloadGate = () => {
const nowIso = "2026-06-08T20:00:00.000Z";
check("now post targets every channel id, content carried through", () => {
const cfg = { contentField: "body", integrationIds: "a, b ,c", when: POST_TYPES.NOW };
const out = buildPostPayload(cfg, { body: "hi" }, [], nowIso);
assert.equal(out.type, POST_TYPES.NOW);
assert.equal(out.date, nowIso);
assert.equal(out.posts.length, 3);
assert.deepEqual(out.posts.map((p) => p.integration.id), ["a", "b", "c"]);
assert.equal(out.posts[0].value[0].content, "hi");
});
check("media ids attach as image refs", () => {
const cfg = { contentField: "body", integrationIds: "a", when: POST_TYPES.NOW };
const out = buildPostPayload(cfg, { body: "pic" }, ["m1", "m2"], nowIso);
assert.deepEqual(out.posts[0].value[0].image, [{ id: "m1" }, { id: "m2" }]);
});
check("scheduled post reads the date field, not now", () => {
const cfg = { contentField: "body", integrationIds: "a", when: POST_TYPES.SCHEDULE, scheduleField: "at" };
const out = buildPostPayload(cfg, { body: "later", at: "2026-12-25T09:00:00Z" }, [], nowIso);
assert.equal(out.type, POST_TYPES.SCHEDULE);
assert.equal(out.date, "2026-12-25T09:00:00.000Z");
});
check("blank / whitespace channel ids are dropped", () => {
const cfg = { contentField: "body", integrationIds: " , ,", when: POST_TYPES.DRAFT };
const out = buildPostPayload(cfg, { body: "x" }, [], nowIso);
assert.equal(out.posts.length, 0);
});
};
const secretsGate = () => {
// secretsAtRest reads the key root from this env var (see getSessionSecret).
process.env.SALTCORN_SESSION_SECRET = process.env.SALTCORN_SESSION_SECRET || "test-session-secret-do-not-use";
const info = "postiz-poster:apiKey";
check("seal -> open round-trips the plaintext", () => {
const blob = encryptSecret("super-secret-key", info);
assert.ok(isEncryptedBlob(blob), "blob should carry the scs1: prefix");
assert.notEqual(blob, "super-secret-key");
assert.equal(decryptSecret(blob, info), "super-secret-key");
});
check("ciphertext differs each call (random IV)", () => {
assert.notEqual(encryptSecret("x", info), encryptSecret("x", info));
});
check("wrong domain label cannot open the blob", () => {
const blob = encryptSecret("k", info);
assert.equal(decryptSecret(blob, "postiz-poster:other"), null);
});
check("tampered blob fails GCM auth -> null", () => {
const blob = encryptSecret("k", info);
const i = "scs1:".length + 2;
const flipped = blob[i] === "A" ? "B" : "A";
const tampered = blob.slice(0, i) + flipped + blob.slice(i + 1);
assert.equal(decryptSecret(tampered, info), null);
});
check("plaintext is not mistaken for an encrypted blob", () => {
assert.equal(isEncryptedBlob("plain"), false);
assert.equal(decryptSecret("plain", info), null);
});
};
const liveGate = async () => {
const baseUrl = process.env.POSTIZ_BASE_URL;
const apiKey = process.env.POSTIZ_API_KEY;
if (!baseUrl || !apiKey) {
console.log("skip - liveGate (set POSTIZ_BASE_URL + POSTIZ_API_KEY to enable)");
return;
}
const client = makePostizClient({ baseUrl, apiKey });
let firstChannel = null;
await checkAsync("listIntegrations returns an array", async () => {
const integrations = await client.listIntegrations();
assert.ok(Array.isArray(integrations), "expected an array of integrations");
firstChannel = integrations[0] || null;
});
await checkAsync("createPost (draft) is accepted", async () => {
if (!firstChannel) {
console.log(" (no channels connected -- connect one in Postiz to exercise createPost)");
return;
}
const cfg = { contentField: "body", integrationIds: String(firstChannel.id), when: POST_TYPES.DRAFT };
const payload = buildPostPayload(cfg, { body: "postiz-poster e2e draft -- safe to delete" }, [], new Date().toISOString());
const res = await client.createPost(payload);
assert.ok(res, "expected a create-post response");
});
};
const main = async () => {
payloadGate();
secretsGate();
await liveGate();
console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed === 0 ? 0 : 1);
};
main();