Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/commands/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ export const DOCUMENTS_META: DomainMeta = {
name: "documents",
summary: "long-form markdown docs attached to projects or issues",
context: [
"a document is a markdown page. it can belong to a project and/or be",
"attached to an issue. documents support icons and colors.",
"a document is a markdown page. it must belong to exactly one project,",
"team, or issue. documents support icons and colors.",
].join("\n"),
arguments: {
document: "document identifier (UUID)",
Expand Down Expand Up @@ -190,8 +190,17 @@ export function setupDocumentsCommands(program: Command): void {
"cannot be combined with --issue",
);
}

const issueIdentifier = options.issue ?? options.attachTo;
const scopes = [options.project, options.team, issueIdentifier].filter(
Boolean,
);
if (scopes.length !== 1) {
throw invalidParameterError(
"--project|--team|--issue",
"a document needs exactly one scope: --project, --team, or --issue",
);
}

const rootOpts = getRootOpts(command);
const ctx = createContext(rootOpts);

Expand Down
68 changes: 63 additions & 5 deletions tests/unit/commands/documents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ vi.mock("../../../src/services/document-service.js", async (importOriginal) => {

import { setupDocumentsCommands } from "../../../src/commands/documents.js";
import { resolveIssueId } from "../../../src/resolvers/issue-resolver.js";
import { resolveTeamId } from "../../../src/resolvers/team-resolver.js";
import { listAttachments } from "../../../src/services/attachment-service.js";
import {
createDocument,
Expand Down Expand Up @@ -167,6 +166,33 @@ describe("documents create", () => {
);
});

it("rejects creating without a document scope", async () => {
const exitSpy = vi
.spyOn(process, "exit")
.mockImplementation(() => undefined as never);
vi.spyOn(console, "error").mockImplementation(() => {});

const program = createProgram();
await program.parseAsync([
"node",
"test",
"documents",
"create",
"--title",
"Runbook",
]);

expect(console.error).toHaveBeenCalledWith(
expect.stringContaining(
"Invalid --project|--team|--issue: a document needs exactly one scope: --project, --team, or --issue",
),
);
expect(createDocument).not.toHaveBeenCalled();
expect(exitSpy).toHaveBeenCalledWith(1);

exitSpy.mockRestore();
});

it("accepts --attach-to as an alias for --issue", async () => {
const program = createProgram();
await program.parseAsync([
Expand All @@ -176,23 +202,55 @@ describe("documents create", () => {
"create",
"--title",
"Runbook",
"--team",
"ENG",
"--attach-to",
"ENG-42",
]);

expect(resolveTeamId).toHaveBeenCalledWith(expect.anything(), "ENG");
expect(resolveIssueId).toHaveBeenCalledWith(expect.anything(), "ENG-42");
expect(createDocument).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
teamId: "resolved-team-uuid",
issueId: "resolved-issue-uuid",
}),
);
});

it.each([
["--project", "PROJ", "--team", "ENG"],
["--project", "PROJ", "--issue", "ENG-42"],
["--team", "ENG", "--issue", "ENG-42"],
])(
"rejects combining %s and %s",
async (firstFlag, firstValue, secondFlag, secondValue) => {
const exitSpy = vi
.spyOn(process, "exit")
.mockImplementation(() => undefined as never);
vi.spyOn(console, "error").mockImplementation(() => {});

const program = createProgram();
await program.parseAsync([
"node",
"test",
"documents",
"create",
"--title",
"Runbook",
firstFlag,
firstValue,
secondFlag,
secondValue,
]);

expect(console.error).toHaveBeenCalledWith(
expect.stringContaining("a document needs exactly one scope"),
);
expect(createDocument).not.toHaveBeenCalled();
expect(exitSpy).toHaveBeenCalledWith(1);

exitSpy.mockRestore();
},
);

it("rejects combining --issue and --attach-to before creating", async () => {
const exitSpy = vi
.spyOn(process, "exit")
Expand Down