Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

128 lines
5.9 KiB
TypeScript

// Contract test for the prompts the plugin-folder card buttons send to the
// agent. `install` uses the simple shared template; `contribute` drives the
// `gh repo fork → branch → commit → gh pr create --web` flow against
// `nexu-io/open-design`; `publish` drives `gh repo create / push` against the
// author's own `plugin.repo` URL. The tests below lock the *shape* of each
// prompt (keywords + folder interpolation) without coupling to exact wording,
// so prose tweaks don't break the suite but accidental removal of a critical
// step would.
import { describe, expect, it } from 'vitest';
import { buildPluginFolderAgentActionPrompt } from '../../src/components/design-files/pluginFolderActions';
const FOLDER = 'generated-plugin';
describe('buildPluginFolderAgentActionPrompt', () => {
describe('install', () => {
it('mentions the folder path and the supported install CLI', () => {
const prompt = buildPluginFolderAgentActionPrompt(FOLDER, 'install');
expect(prompt).toContain(`Plugin folder: \`${FOLDER}\``);
expect(prompt).toContain('od plugin install --source');
});
});
describe('publish (repo-publish flow)', () => {
const prompt = buildPluginFolderAgentActionPrompt(FOLDER, 'publish');
it('delegates repo publishing to the deterministic plugin CLI helper', () => {
expect(prompt).toContain(`Plugin folder: \`${FOLDER}\``);
expect(prompt).toContain(`"$OD_NODE_BIN" "$OD_BIN" plugin publish-repo ${FOLDER}`);
expect(prompt).toMatch(/current gh login|target is not hard-coded/i);
});
it('states the CLI-owned responsibilities instead of re-listing shell steps', () => {
expect(prompt).toContain('gh api user --jq .login');
expect(prompt).toContain('--owner');
expect(prompt).toMatch(/last-resort fallback/i);
expect(prompt).toMatch(/placeholder owners are rejected/i);
expect(prompt).toMatch(/manifest repo normalization/i);
expect(prompt).toMatch(/repo existence check/i);
expect(prompt).toMatch(/repo create\/update/i);
expect(prompt).toMatch(/final verification/i);
});
it('bans the registry-submission CLI explicitly', () => {
// The legacy CLI is what shipped the bug — without an explicit ban
// the agent had been routing back to it. The mention must be in a
// negative imperative ("Do NOT call …"), not a recommendation.
expect(prompt).toMatch(
/Do NOT (call|route through) `?od plugin publish --to open-design`?/i,
);
expect(prompt).toMatch(
/registry[- ]submission|registry-submission flow|Open Design PR/i,
);
});
it('hard-bans mid-turn clarification + auto-install + force-push + retry', () => {
expect(prompt).toContain('<question-form>');
expect(prompt).toMatch(/fire-and-forget|clarification UI that waits/i);
expect(prompt).toMatch(/do not try to install/i);
expect(prompt).toMatch(/do not force-push|--force/i);
expect(prompt).toMatch(/do not retry/i);
});
it('interpolates the actual folder path into the CLI command', () => {
// Sanity check that template-string interpolation didn't regress into
// literal `${folderPath}` substrings.
expect(prompt).toContain(`plugin publish-repo ${FOLDER}`);
expect(prompt).not.toContain('${folderPath}');
});
it('ends by handing the repo URL back to chat', () => {
expect(prompt).toMatch(/final repo URL printed by the CLI/i);
});
});
describe('contribute (PR-based flow)', () => {
const prompt = buildPluginFolderAgentActionPrompt(FOLDER, 'contribute');
it('delegates Open Design PR creation to the deterministic plugin CLI helper', () => {
expect(prompt).toContain('nexu-io/open-design');
expect(prompt).toContain(`"$OD_NODE_BIN" "$OD_BIN" plugin open-design-pr ${FOLDER}`);
});
it('states the CLI-owned PR workflow instead of re-listing shell steps', () => {
expect(prompt).toContain('gh api user --jq .login');
expect(prompt).toContain('--owner');
expect(prompt).toMatch(/last-resort fallback/i);
expect(prompt).toMatch(/fork\/clone\/copy\/branch\/push/i);
expect(prompt).toContain('gh pr create --web');
// The legacy CLI is named in the prompt only as part of an explicit
// ban ("Do NOT call the legacy `od plugin publish --to open-design`")
// — verify the ban is in place, not the bare command.
expect(prompt).toMatch(/do not call the legacy `od plugin publish --to open-design`/i);
});
it('uses --web so the author confirms the PR in browser', () => {
// The "author keeps the final review click" invariant — preserved from
// 45f52d71's "We never POST anywhere" principle.
expect(prompt).toContain('--web');
expect(prompt).toMatch(/do not auto-submit/i);
});
it('hard-bans mid-turn clarification forms to avoid 600s stalls', () => {
// Regression guard for the stall we observed during e2e: agent paused
// mid-turn on a clarification waiting for a host answer the user never
// sent (they clicked the plugin-folder card instead).
expect(prompt).toContain('<question-form>');
expect(prompt).toMatch(/clarification UI that waits|fire-and-forget/i);
});
it('forbids the agent from installing tools or retrying failures', () => {
expect(prompt).toMatch(/do not try to install/i);
expect(prompt).toMatch(/do not retry/i);
});
it('interpolates the actual folder path into manifest and copy steps', () => {
// Sanity check that template-string interpolation didn't regress into
// literal `${folderPath}` substrings (we already shipped that bug once).
expect(prompt).toContain(`plugin open-design-pr ${FOLDER}`);
expect(prompt).not.toContain('${folderPath}');
});
it('ends by handing the PR URL back to chat', () => {
expect(prompt).toMatch(/PR URL|pull\/new|paste it into chat/);
});
});
});