322 lines
8.0 KiB
TypeScript
322 lines
8.0 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { markdownToAdf } from "../src/adf.js";
|
|
import { createTempWorkspace, jsonResponse, runCli } from "./helpers.js";
|
|
|
|
const baseEnv = {
|
|
ATLASSIAN_BASE_URL: "https://example.atlassian.net",
|
|
ATLASSIAN_EMAIL: "dev@example.com",
|
|
ATLASSIAN_API_TOKEN: "secret-token",
|
|
};
|
|
|
|
test("jira-search emits normalized results and uses pagination inputs", async () => {
|
|
const calls: Array<{ url: string; init: RequestInit | undefined }> = [];
|
|
|
|
const fetchImpl: typeof fetch = async (input, init) => {
|
|
const url = typeof input === "string" ? input : input.toString();
|
|
calls.push({ url, init });
|
|
|
|
return jsonResponse({
|
|
startAt: 10,
|
|
maxResults: 2,
|
|
total: 25,
|
|
issues: [
|
|
{
|
|
key: "ENG-1",
|
|
fields: {
|
|
summary: "Add Jira search command",
|
|
issuetype: { name: "Story" },
|
|
status: { name: "In Progress" },
|
|
assignee: { displayName: "Ada Lovelace" },
|
|
created: "2026-03-01T00:00:00.000Z",
|
|
updated: "2026-03-02T00:00:00.000Z",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
};
|
|
|
|
const result = await runCli({
|
|
args: ["jira-search", "--jql", "project = ENG", "--max-results", "2", "--start-at", "10"],
|
|
env: baseEnv,
|
|
fetchImpl,
|
|
});
|
|
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0]?.url, "https://example.atlassian.net/rest/api/3/search");
|
|
assert.equal(calls[0]?.init?.method, "POST");
|
|
assert.match(String(calls[0]?.init?.headers), /Authorization/);
|
|
assert.deepEqual(JSON.parse(String(calls[0]?.init?.body)), {
|
|
jql: "project = ENG",
|
|
maxResults: 2,
|
|
startAt: 10,
|
|
fields: ["summary", "issuetype", "status", "assignee", "created", "updated"],
|
|
});
|
|
|
|
assert.deepEqual(JSON.parse(result.stdout), {
|
|
ok: true,
|
|
data: {
|
|
issues: [
|
|
{
|
|
key: "ENG-1",
|
|
summary: "Add Jira search command",
|
|
issueType: "Story",
|
|
status: "In Progress",
|
|
assignee: "Ada Lovelace",
|
|
created: "2026-03-01T00:00:00.000Z",
|
|
updated: "2026-03-02T00:00:00.000Z",
|
|
url: "https://example.atlassian.net/browse/ENG-1",
|
|
},
|
|
],
|
|
startAt: 10,
|
|
maxResults: 2,
|
|
total: 25,
|
|
},
|
|
});
|
|
});
|
|
|
|
test("jira-get returns normalized fields plus the raw Jira payload", async () => {
|
|
const rawIssue = {
|
|
key: "ENG-42",
|
|
fields: {
|
|
summary: "Ship v1",
|
|
issuetype: { name: "Task" },
|
|
status: { name: "Done" },
|
|
assignee: { displayName: "Grace Hopper" },
|
|
created: "2026-03-03T00:00:00.000Z",
|
|
updated: "2026-03-04T00:00:00.000Z",
|
|
},
|
|
};
|
|
|
|
const fetchImpl: typeof fetch = async () => jsonResponse(rawIssue);
|
|
|
|
const result = await runCli({
|
|
args: ["jira-get", "--issue", "ENG-42"],
|
|
env: baseEnv,
|
|
fetchImpl,
|
|
});
|
|
|
|
assert.deepEqual(JSON.parse(result.stdout), {
|
|
ok: true,
|
|
data: {
|
|
issue: {
|
|
key: "ENG-42",
|
|
summary: "Ship v1",
|
|
issueType: "Task",
|
|
status: "Done",
|
|
assignee: "Grace Hopper",
|
|
created: "2026-03-03T00:00:00.000Z",
|
|
updated: "2026-03-04T00:00:00.000Z",
|
|
url: "https://example.atlassian.net/browse/ENG-42",
|
|
},
|
|
},
|
|
raw: rawIssue,
|
|
});
|
|
});
|
|
|
|
test("markdownToAdf converts headings, paragraphs, and bullet lists", () => {
|
|
assert.deepEqual(markdownToAdf("# Summary\n\nBuild the Jira skill.\n\n- Search\n- Comment"), {
|
|
type: "doc",
|
|
version: 1,
|
|
content: [
|
|
{
|
|
type: "heading",
|
|
attrs: { level: 1 },
|
|
content: [{ type: "text", text: "Summary" }],
|
|
},
|
|
{
|
|
type: "paragraph",
|
|
content: [{ type: "text", text: "Build the Jira skill." }],
|
|
},
|
|
{
|
|
type: "bulletList",
|
|
content: [
|
|
{
|
|
type: "listItem",
|
|
content: [
|
|
{
|
|
type: "paragraph",
|
|
content: [{ type: "text", text: "Search" }],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "listItem",
|
|
content: [
|
|
{
|
|
type: "paragraph",
|
|
content: [{ type: "text", text: "Comment" }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
test("jira-create dry-run emits an ADF request body without calling Jira", async () => {
|
|
const workspace = createTempWorkspace();
|
|
|
|
try {
|
|
workspace.write("description.md", "# New story\n\n- one\n- two");
|
|
let called = false;
|
|
|
|
const result = await runCli({
|
|
args: [
|
|
"jira-create",
|
|
"--type",
|
|
"Story",
|
|
"--summary",
|
|
"Create the Atlassian skill",
|
|
"--description-file",
|
|
"description.md",
|
|
"--dry-run",
|
|
],
|
|
cwd: workspace.cwd,
|
|
env: {
|
|
...baseEnv,
|
|
ATLASSIAN_DEFAULT_PROJECT: "ENG",
|
|
},
|
|
fetchImpl: async () => {
|
|
called = true;
|
|
return jsonResponse({});
|
|
},
|
|
});
|
|
|
|
assert.equal(called, false);
|
|
assert.deepEqual(JSON.parse(result.stdout), {
|
|
ok: true,
|
|
dryRun: true,
|
|
data: {
|
|
method: "POST",
|
|
url: "https://example.atlassian.net/rest/api/3/issue",
|
|
body: {
|
|
fields: {
|
|
project: { key: "ENG" },
|
|
issuetype: { name: "Story" },
|
|
summary: "Create the Atlassian skill",
|
|
description: markdownToAdf("# New story\n\n- one\n- two"),
|
|
},
|
|
},
|
|
},
|
|
});
|
|
} finally {
|
|
workspace.cleanup();
|
|
}
|
|
});
|
|
|
|
test("jira-update, jira-comment, and jira-transition dry-runs build the expected Jira requests", async () => {
|
|
const workspace = createTempWorkspace();
|
|
|
|
try {
|
|
workspace.write("issue.md", "Updated description");
|
|
workspace.write("comment.md", "Comment body");
|
|
|
|
const update = await runCli({
|
|
args: [
|
|
"jira-update",
|
|
"--issue",
|
|
"ENG-9",
|
|
"--summary",
|
|
"Updated summary",
|
|
"--description-file",
|
|
"issue.md",
|
|
"--dry-run",
|
|
],
|
|
cwd: workspace.cwd,
|
|
env: baseEnv,
|
|
});
|
|
|
|
const comment = await runCli({
|
|
args: ["jira-comment", "--issue", "ENG-9", "--body-file", "comment.md", "--dry-run"],
|
|
cwd: workspace.cwd,
|
|
env: baseEnv,
|
|
});
|
|
|
|
const transition = await runCli({
|
|
args: ["jira-transition", "--issue", "ENG-9", "--transition", "31", "--dry-run"],
|
|
cwd: workspace.cwd,
|
|
env: baseEnv,
|
|
});
|
|
|
|
assert.deepEqual(JSON.parse(update.stdout), {
|
|
ok: true,
|
|
dryRun: true,
|
|
data: {
|
|
method: "PUT",
|
|
url: "https://example.atlassian.net/rest/api/3/issue/ENG-9",
|
|
body: {
|
|
fields: {
|
|
summary: "Updated summary",
|
|
description: markdownToAdf("Updated description"),
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(JSON.parse(comment.stdout), {
|
|
ok: true,
|
|
dryRun: true,
|
|
data: {
|
|
method: "POST",
|
|
url: "https://example.atlassian.net/rest/api/3/issue/ENG-9/comment",
|
|
body: {
|
|
body: markdownToAdf("Comment body"),
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(JSON.parse(transition.stdout), {
|
|
ok: true,
|
|
dryRun: true,
|
|
data: {
|
|
method: "POST",
|
|
url: "https://example.atlassian.net/rest/api/3/issue/ENG-9/transitions",
|
|
body: {
|
|
transition: {
|
|
id: "31",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
} finally {
|
|
workspace.cleanup();
|
|
}
|
|
});
|
|
|
|
test("jira-transitions returns normalized transition options", async () => {
|
|
const fetchImpl: typeof fetch = async () =>
|
|
jsonResponse({
|
|
transitions: [
|
|
{
|
|
id: "21",
|
|
name: "Start Progress",
|
|
to: { name: "In Progress" },
|
|
hasScreen: false,
|
|
},
|
|
],
|
|
});
|
|
|
|
const result = await runCli({
|
|
args: ["jira-transitions", "--issue", "ENG-9"],
|
|
env: baseEnv,
|
|
fetchImpl,
|
|
});
|
|
|
|
assert.deepEqual(JSON.parse(result.stdout), {
|
|
ok: true,
|
|
data: {
|
|
transitions: [
|
|
{
|
|
id: "21",
|
|
name: "Start Progress",
|
|
toStatus: "In Progress",
|
|
hasScreen: false,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|