2026-03-04 16:29:14 -06:00
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
|
import type { Request } from "express";
|
|
|
|
|
import { buildInviteOnboardingTextDocument } from "../routes/access.js";
|
|
|
|
|
|
|
|
|
|
function buildReq(host: string): Request {
|
|
|
|
|
return {
|
|
|
|
|
protocol: "http",
|
|
|
|
|
header(name: string) {
|
|
|
|
|
if (name.toLowerCase() === "host") return host;
|
|
|
|
|
return undefined;
|
|
|
|
|
},
|
|
|
|
|
} as unknown as Request;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("buildInviteOnboardingTextDocument", () => {
|
|
|
|
|
it("renders a plain-text onboarding doc with expected endpoint references", () => {
|
|
|
|
|
const req = buildReq("localhost:3100");
|
|
|
|
|
const invite = {
|
|
|
|
|
id: "invite-1",
|
|
|
|
|
companyId: "company-1",
|
|
|
|
|
inviteType: "company_join",
|
|
|
|
|
allowedJoinTypes: "agent",
|
|
|
|
|
tokenHash: "hash",
|
|
|
|
|
defaultsPayload: null,
|
|
|
|
|
expiresAt: new Date("2026-03-05T00:00:00.000Z"),
|
|
|
|
|
invitedByUserId: null,
|
|
|
|
|
revokedAt: null,
|
|
|
|
|
acceptedAt: null,
|
|
|
|
|
createdAt: new Date("2026-03-04T00:00:00.000Z"),
|
|
|
|
|
updatedAt: new Date("2026-03-04T00:00:00.000Z"),
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const text = buildInviteOnboardingTextDocument(req, "token-123", invite as any, {
|
|
|
|
|
deploymentMode: "local_trusted",
|
|
|
|
|
deploymentExposure: "private",
|
|
|
|
|
bindHost: "127.0.0.1",
|
|
|
|
|
allowedHostnames: [],
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-07 15:39:12 -06:00
|
|
|
expect(text).toContain("Paperclip OpenClaw Gateway Onboarding");
|
2026-03-04 16:29:14 -06:00
|
|
|
expect(text).toContain("/api/invites/token-123/accept");
|
|
|
|
|
expect(text).toContain("/api/join-requests/{requestId}/claim-api-key");
|
|
|
|
|
expect(text).toContain("/api/invites/token-123/onboarding.txt");
|
[codex] harden authenticated routes and issue editor reliability (#3741)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The control plane depends on authenticated routes enforcing company
boundaries and role permissions correctly
> - This branch also touches the issue detail and markdown editing flows
operators use while handling advisory and triage work
> - Partial issue cache seeds and fragile rich-editor parsing could
leave important issue content missing or blank at the moment an operator
needed it
> - Blocked issues becoming actionable again should wake their assignee
automatically instead of silently staying idle
> - This pull request rebases the advisory follow-up branch onto current
`master`, hardens authenticated route authorization, and carries the
issue-detail/editor reliability fixes forward with regression tests
> - The benefit is tighter authz on sensitive routes plus more reliable
issue/advisory editing and wakeup behavior on top of the latest base
## What Changed
- Hardened authenticated route authorization across agent, activity,
approval, access, project, plugin, health, execution-workspace,
portability, and related server paths, with new cross-tenant and
runtime-authz regression coverage.
- Switched issue detail queries from `initialData` to placeholder-based
hydration so list/quicklook seeds still refetch full issue bodies.
- Normalized advisory-style HTML images before mounting the markdown
editor and strengthened fallback behavior when the rich editor silently
fails or rejects the content.
- Woke assigned agents when blocked issues move back to `todo`, with
route coverage for reopen and unblock transitions.
- Rebasing note: this branch now sits cleanly on top of the latest
`master` tip used for the PR base.
## Verification
- `pnpm exec vitest run ui/src/lib/issueDetailQuery.test.tsx
ui/src/components/MarkdownEditor.test.tsx
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/activity-routes.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts`
- Confirmed `pnpm-lock.yaml` is not part of the PR diff.
- Rebased the branch onto current `public-gh/master` before publishing.
## Risks
- Broad authz tightening may expose existing flows that were relying on
permissive board or agent access and now need explicit grants.
- Markdown editor fallback changes could affect focus or rendering in
edge-case content that mixes HTML-like advisory markup with normal
markdown.
- This verification was intentionally scoped to touched regressions and
did not run the full repository suite.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment
with tool use for terminal, git, and GitHub operations. The exact
runtime model identifier is not exposed inside this session.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, it is behavior-only and does not
need before/after screenshots
- [x] I have updated relevant documentation to reflect my changes, or no
documentation changes were needed for these internal fixes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 08:41:15 -05:00
|
|
|
expect(text).toContain("/api/invites/token-123/skills/paperclip");
|
2026-03-05 12:28:27 -06:00
|
|
|
expect(text).toContain("Suggested Paperclip base URLs to try");
|
|
|
|
|
expect(text).toContain("http://localhost:3100");
|
|
|
|
|
expect(text).toContain("host.docker.internal");
|
2026-03-06 08:39:29 -06:00
|
|
|
expect(text).toContain("paperclipApiUrl");
|
2026-03-07 15:39:12 -06:00
|
|
|
expect(text).toContain("adapterType \"openclaw_gateway\"");
|
|
|
|
|
expect(text).toContain("headers.x-openclaw-token");
|
|
|
|
|
expect(text).toContain("Do NOT use /v1/responses or /hooks/*");
|
2026-03-06 08:39:29 -06:00
|
|
|
expect(text).toContain("set the first reachable candidate as agentDefaultsPayload.paperclipApiUrl");
|
2026-03-06 11:21:55 -06:00
|
|
|
expect(text).toContain("~/.openclaw/workspace/paperclip-claimed-api-key.json");
|
|
|
|
|
expect(text).toContain("PAPERCLIP_API_KEY");
|
|
|
|
|
expect(text).toContain("saved token field");
|
2026-03-07 16:01:19 -06:00
|
|
|
expect(text).toContain("Gateway token unexpectedly short");
|
2026-03-04 16:29:14 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("includes loopback diagnostics for authenticated/private onboarding", () => {
|
|
|
|
|
const req = buildReq("localhost:3100");
|
|
|
|
|
const invite = {
|
|
|
|
|
id: "invite-2",
|
|
|
|
|
companyId: "company-1",
|
|
|
|
|
inviteType: "company_join",
|
|
|
|
|
allowedJoinTypes: "both",
|
|
|
|
|
tokenHash: "hash",
|
|
|
|
|
defaultsPayload: null,
|
|
|
|
|
expiresAt: new Date("2026-03-05T00:00:00.000Z"),
|
|
|
|
|
invitedByUserId: null,
|
|
|
|
|
revokedAt: null,
|
|
|
|
|
acceptedAt: null,
|
|
|
|
|
createdAt: new Date("2026-03-04T00:00:00.000Z"),
|
|
|
|
|
updatedAt: new Date("2026-03-04T00:00:00.000Z"),
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const text = buildInviteOnboardingTextDocument(req, "token-456", invite as any, {
|
|
|
|
|
deploymentMode: "authenticated",
|
|
|
|
|
deploymentExposure: "private",
|
|
|
|
|
bindHost: "127.0.0.1",
|
|
|
|
|
allowedHostnames: [],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(text).toContain("Connectivity diagnostics");
|
|
|
|
|
expect(text).toContain("loopback hostname");
|
2026-03-05 12:28:27 -06:00
|
|
|
expect(text).toContain("If none are reachable");
|
2026-03-04 16:29:14 -06:00
|
|
|
});
|
2026-03-05 12:10:01 -06:00
|
|
|
|
|
|
|
|
it("includes inviter message in the onboarding text when provided", () => {
|
|
|
|
|
const req = buildReq("localhost:3100");
|
|
|
|
|
const invite = {
|
|
|
|
|
id: "invite-3",
|
|
|
|
|
companyId: "company-1",
|
|
|
|
|
inviteType: "company_join",
|
|
|
|
|
allowedJoinTypes: "agent",
|
|
|
|
|
tokenHash: "hash",
|
|
|
|
|
defaultsPayload: {
|
|
|
|
|
agentMessage: "Please join as our QA lead and prioritize flaky test triage first.",
|
|
|
|
|
},
|
|
|
|
|
expiresAt: new Date("2026-03-05T00:00:00.000Z"),
|
|
|
|
|
invitedByUserId: null,
|
|
|
|
|
revokedAt: null,
|
|
|
|
|
acceptedAt: null,
|
|
|
|
|
createdAt: new Date("2026-03-04T00:00:00.000Z"),
|
|
|
|
|
updatedAt: new Date("2026-03-04T00:00:00.000Z"),
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const text = buildInviteOnboardingTextDocument(req, "token-789", invite as any, {
|
|
|
|
|
deploymentMode: "local_trusted",
|
|
|
|
|
deploymentExposure: "private",
|
|
|
|
|
bindHost: "127.0.0.1",
|
|
|
|
|
allowedHostnames: [],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(text).toContain("Message from inviter");
|
|
|
|
|
expect(text).toContain("prioritize flaky test triage first");
|
|
|
|
|
});
|
2026-03-04 16:29:14 -06:00
|
|
|
});
|