mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 10:26:26 +01:00
fix(smoke): use scoped PostHog live bindings
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
eb962ef283
commit
0b2759fcca
4 files changed
+99
-27
No files matched your search
+9
-7
@@ -1032,15 +1032,17 @@ uses a live vendor OAuth flow and creates a short-lived connection plus one
|
||||
fresh-run proof issue.
|
||||
|
||||
```sh
|
||||
PAPERCLIP_E2E_BASE_URL=https://paperclip.example.test \
|
||||
PAPERCLIP_E2E_EMAIL=operator@example.test \
|
||||
PAPERCLIP_DEV_LOGIN_PASSWORD='<environment-delivered>' \
|
||||
POSTHOG_PROJECT_ID=483530 \
|
||||
pnpm smoke:posthog-live
|
||||
INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL=operator@example.test \
|
||||
INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD='<environment-delivered>' \
|
||||
INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID=483530 \
|
||||
pnpm smoke:posthog-live https://paperclip.example.test
|
||||
```
|
||||
|
||||
The command fails before browser launch unless all four variables are present,
|
||||
and never prints their values. It creates no trace, video, or HAR, begins
|
||||
The command fails before browser launch unless all three integration bindings
|
||||
are present, and never prints their values. A Paperclip heartbeat derives the
|
||||
target origin from its injected `PAPERCLIP_API_URL`; the positional URL (or
|
||||
`--base-url <url>`) selects the running instance for a manual invocation. It
|
||||
creates no trace, video, or HAR, begins
|
||||
screenshots only after OAuth returns to Paperclip, enables read actions only,
|
||||
installs the connection on `CodexCoderPro` only, runs `project-get` with `{}`
|
||||
from the board Test panel and a fresh agent run, then removes the connection.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const REQUIRED_ENVIRONMENT = [
|
||||
"PAPERCLIP_E2E_BASE_URL",
|
||||
"PAPERCLIP_E2E_EMAIL",
|
||||
"PAPERCLIP_DEV_LOGIN_PASSWORD",
|
||||
"POSTHOG_PROJECT_ID",
|
||||
"INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL",
|
||||
"INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD",
|
||||
"INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID",
|
||||
];
|
||||
const REQUIRED_PROJECT_ID = "483530";
|
||||
|
||||
export class PosthogLivePreflightError extends Error {
|
||||
constructor(code, details = {}) {
|
||||
@@ -19,15 +19,31 @@ function requiredValue(environment, key) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
export function preflightPosthogLive(environment = process.env) {
|
||||
export function parsePosthogLiveArguments(args = []) {
|
||||
if (args.length === 0) return {};
|
||||
if (args.length === 1 && !args[0].startsWith("-")) return { baseUrl: args[0] };
|
||||
if (args.length === 2 && args[0] === "--base-url" && args[1].trim()) {
|
||||
return { baseUrl: args[1] };
|
||||
}
|
||||
throw new PosthogLivePreflightError("invalid_arguments");
|
||||
}
|
||||
|
||||
export function preflightPosthogLive(environment = process.env, options = {}) {
|
||||
const missing = REQUIRED_ENVIRONMENT.filter((key) => requiredValue(environment, key) === "");
|
||||
if (missing.length > 0) {
|
||||
throw new PosthogLivePreflightError("missing_environment", { missing });
|
||||
}
|
||||
|
||||
const baseUrlValue = typeof options.baseUrl === "string" && options.baseUrl.trim()
|
||||
? options.baseUrl.trim()
|
||||
: requiredValue(environment, "PAPERCLIP_API_URL");
|
||||
if (!baseUrlValue) {
|
||||
throw new PosthogLivePreflightError("missing_base_url");
|
||||
}
|
||||
|
||||
let baseUrl;
|
||||
try {
|
||||
baseUrl = new URL(requiredValue(environment, "PAPERCLIP_E2E_BASE_URL"));
|
||||
baseUrl = new URL(baseUrlValue);
|
||||
} catch {
|
||||
throw new PosthogLivePreflightError("invalid_base_url");
|
||||
}
|
||||
@@ -41,11 +57,14 @@ export function preflightPosthogLive(environment = process.env) {
|
||||
}
|
||||
baseUrl.pathname = baseUrl.pathname.replace(/\/+$/, "") || "/";
|
||||
|
||||
const projectId = requiredValue(environment, "POSTHOG_PROJECT_ID");
|
||||
const projectId = requiredValue(environment, "INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID");
|
||||
if (!/^\d+$/.test(projectId)) {
|
||||
throw new PosthogLivePreflightError("invalid_project_id");
|
||||
}
|
||||
const email = requiredValue(environment, "PAPERCLIP_E2E_EMAIL");
|
||||
if (projectId !== REQUIRED_PROJECT_ID) {
|
||||
throw new PosthogLivePreflightError("unexpected_project_id");
|
||||
}
|
||||
const email = requiredValue(environment, "INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL");
|
||||
if (!email.includes("@")) {
|
||||
throw new PosthogLivePreflightError("invalid_email");
|
||||
}
|
||||
@@ -53,17 +72,18 @@ export function preflightPosthogLive(environment = process.env) {
|
||||
return {
|
||||
baseUrl: baseUrl.origin,
|
||||
email,
|
||||
password: environment.PAPERCLIP_DEV_LOGIN_PASSWORD,
|
||||
password: environment.INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD,
|
||||
projectId,
|
||||
};
|
||||
}
|
||||
|
||||
export async function preparePosthogLiveSmoke({
|
||||
environment = process.env,
|
||||
baseUrl,
|
||||
fetchImpl = globalThis.fetch,
|
||||
loadBrowser,
|
||||
}) {
|
||||
const config = preflightPosthogLive(environment);
|
||||
const config = preflightPosthogLive(environment, { baseUrl });
|
||||
let response;
|
||||
try {
|
||||
response = await fetchImpl(new URL("/api/health", config.baseUrl), {
|
||||
@@ -217,5 +237,11 @@ export function preflightFailureMessage(error) {
|
||||
if (error.code === "health_http_error") {
|
||||
return `PostHog live smoke preflight failed: /api/health returned HTTP ${error.details.status}.`;
|
||||
}
|
||||
if (error.code === "missing_base_url") {
|
||||
return "PostHog live smoke preflight failed: pass the target Paperclip URL or run inside a Paperclip heartbeat.";
|
||||
}
|
||||
if (error.code === "invalid_arguments") {
|
||||
return "PostHog live smoke preflight failed: expected an optional Paperclip URL or --base-url <url>.";
|
||||
}
|
||||
return `PostHog live smoke preflight failed: ${error.code}.`;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import path from "node:path";
|
||||
import {
|
||||
assertSanitizedEvidence,
|
||||
extractProjectSummary,
|
||||
parsePosthogLiveArguments,
|
||||
parseSanitizedAgentProof,
|
||||
PosthogLivePreflightError,
|
||||
preflightFailureMessage,
|
||||
@@ -776,7 +777,9 @@ async function runSmoke({ config, chromium }) {
|
||||
async function main() {
|
||||
let prepared;
|
||||
try {
|
||||
const options = parsePosthogLiveArguments(process.argv.slice(2));
|
||||
prepared = await preparePosthogLiveSmoke({
|
||||
baseUrl: options.baseUrl,
|
||||
loadBrowser: () => import("@playwright/test"),
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import test from "node:test";
|
||||
import {
|
||||
assertSanitizedEvidence,
|
||||
extractProjectSummary,
|
||||
parsePosthogLiveArguments,
|
||||
parseSanitizedAgentProof,
|
||||
PosthogLivePreflightError,
|
||||
preflightPosthogLive,
|
||||
@@ -10,22 +11,24 @@ import {
|
||||
} from "./posthog-live-lib.mjs";
|
||||
|
||||
const COMPLETE_ENV = {
|
||||
PAPERCLIP_E2E_BASE_URL: "https://paperclip.example.test",
|
||||
PAPERCLIP_E2E_EMAIL: "operator@example.test",
|
||||
PAPERCLIP_DEV_LOGIN_PASSWORD: "not-a-real-password",
|
||||
POSTHOG_PROJECT_ID: "483530",
|
||||
PAPERCLIP_API_URL: "https://paperclip.example.test/api",
|
||||
INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL: "operator@example.test",
|
||||
INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD: "not-a-real-password",
|
||||
INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID: "483530",
|
||||
};
|
||||
|
||||
test("preflight reports only missing binding names", () => {
|
||||
assert.throws(
|
||||
() => preflightPosthogLive({ PAPERCLIP_DEV_LOGIN_PASSWORD: "present" }),
|
||||
() => preflightPosthogLive({
|
||||
PAPERCLIP_API_URL: "https://paperclip.example.test/api",
|
||||
INTEGRATIONS_POSTHOG_PAPERCLIP_DEV_LOGIN_PASSWORD: "present",
|
||||
}),
|
||||
(error) => {
|
||||
assert.ok(error instanceof PosthogLivePreflightError);
|
||||
assert.equal(error.code, "missing_environment");
|
||||
assert.deepEqual(error.details.missing, [
|
||||
"PAPERCLIP_E2E_BASE_URL",
|
||||
"PAPERCLIP_E2E_EMAIL",
|
||||
"POSTHOG_PROJECT_ID",
|
||||
"INTEGRATIONS_POSTHOG_PAPERCLIP_E2E_EMAIL",
|
||||
"INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID",
|
||||
]);
|
||||
assert.doesNotMatch(error.message, /present/);
|
||||
return true;
|
||||
@@ -40,16 +43,54 @@ test("preflight rejects credential-bearing and non-HTTPS remote URLs", () => {
|
||||
"http://example.test",
|
||||
]) {
|
||||
assert.throws(
|
||||
() => preflightPosthogLive({ ...COMPLETE_ENV, PAPERCLIP_E2E_BASE_URL: baseUrl }),
|
||||
() => preflightPosthogLive({ ...COMPLETE_ENV, PAPERCLIP_API_URL: baseUrl }),
|
||||
(error) => error instanceof PosthogLivePreflightError && error.code === "unsafe_base_url",
|
||||
);
|
||||
}
|
||||
assert.equal(
|
||||
preflightPosthogLive({ ...COMPLETE_ENV, PAPERCLIP_E2E_BASE_URL: "http://127.0.0.1:3100" }).baseUrl,
|
||||
preflightPosthogLive(COMPLETE_ENV, { baseUrl: "http://127.0.0.1:3100" }).baseUrl,
|
||||
"http://127.0.0.1:3100",
|
||||
);
|
||||
});
|
||||
|
||||
test("preflight derives the current Paperclip origin and accepts an explicit target", () => {
|
||||
assert.equal(preflightPosthogLive(COMPLETE_ENV).baseUrl, "https://paperclip.example.test");
|
||||
assert.equal(
|
||||
preflightPosthogLive(COMPLETE_ENV, { baseUrl: "https://other-paperclip.example.test" }).baseUrl,
|
||||
"https://other-paperclip.example.test",
|
||||
);
|
||||
assert.throws(
|
||||
() => preflightPosthogLive({ ...COMPLETE_ENV, PAPERCLIP_API_URL: "" }),
|
||||
(error) => error instanceof PosthogLivePreflightError && error.code === "missing_base_url",
|
||||
);
|
||||
});
|
||||
|
||||
test("preflight fails closed unless the PostHog project is exactly 483530", () => {
|
||||
assert.throws(
|
||||
() => preflightPosthogLive({
|
||||
...COMPLETE_ENV,
|
||||
INTEGRATIONS_POSTHOG_POSTHOG_PROJECT_ID: "42",
|
||||
}),
|
||||
(error) => error instanceof PosthogLivePreflightError && error.code === "unexpected_project_id",
|
||||
);
|
||||
});
|
||||
|
||||
test("live smoke arguments accept a target URL without another environment binding", () => {
|
||||
assert.deepEqual(parsePosthogLiveArguments([]), {});
|
||||
assert.deepEqual(
|
||||
parsePosthogLiveArguments(["https://paperclip.example.test"]),
|
||||
{ baseUrl: "https://paperclip.example.test" },
|
||||
);
|
||||
assert.deepEqual(
|
||||
parsePosthogLiveArguments(["--base-url", "https://paperclip.example.test"]),
|
||||
{ baseUrl: "https://paperclip.example.test" },
|
||||
);
|
||||
assert.throws(
|
||||
() => parsePosthogLiveArguments(["--unknown"]),
|
||||
(error) => error instanceof PosthogLivePreflightError && error.code === "invalid_arguments",
|
||||
);
|
||||
});
|
||||
|
||||
test("browser loading happens only after binding and health preflight", async () => {
|
||||
let fetchCalled = false;
|
||||
let browserLoaded = false;
|
||||
|
||||
Reference in new issue
Block a user