open-design/apps/web/tests/components/pluginFolderActions.test.ts
shangxinyu1 10e2019c59
Fix plugin publish and Open Design PR workflow UX (#2564)
* Fix plugin publish and PR workflow UX

* Update plugin workflow test expectations

* Fix fake gh repo view verification path

* Fix plugin publish headless tests and preserve PATH in shell wrappers.

The publish-repo flow needs real git commits and fake gh auth output that
matches gh auth status parsing. Login shells no longer drop PATH so test
fakes and agent wrappers stay visible to nested gh/git calls.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Restore plugin action card when share-task startup fails.

If startGeneratedPluginShareTask rejects before a task is created, clear
hiddenAssistantPluginActionPaths so the assistant action card reappears.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Make daemon vitest self-contained for publish-github CLI shell-outs.

Build dist/cli.js in tests/setup.ts when missing and set OD_DAEMON_CLI_PATH
before server.ts resolves OD_BIN, so headless plugin tests pass from a clean
checkout without a prior manual daemon build.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 19:21:17 +08:00

127 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 AskUserQuestion + auto-install + force-push + retry', () => {
expect(prompt).toContain('AskUserQuestion');
expect(prompt).toMatch(/fire-and-forget|do not call the `AskUserQuestion`/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 AskUserQuestion to avoid 600s mid-turn stalls', () => {
// Regression guard for the stall we observed during e2e: agent paused
// mid-turn on an AskUserQuestion tool waiting for a host answer the
// user never sent (they clicked the plugin-folder card instead).
expect(prompt).toContain('AskUserQuestion');
expect(prompt).toMatch(/do not call the `AskUserQuestion` tool|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/);
});
});
});