162 lines
6 KiB
JavaScript
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();
|