@sweny-ai/core 0.1.76 → 0.1.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/browser.d.ts +11 -5
  2. package/dist/browser.js +11 -11
  3. package/dist/claude.d.ts +20 -0
  4. package/dist/claude.js +139 -9
  5. package/dist/cli/config.d.ts +22 -0
  6. package/dist/cli/config.js +56 -9
  7. package/dist/cli/main.d.ts +1 -1
  8. package/dist/cli/main.js +56 -57
  9. package/dist/cli/marketplace.js +35 -9
  10. package/dist/cli/publish.d.ts +10 -0
  11. package/dist/cli/publish.js +22 -14
  12. package/dist/executor.js +104 -43
  13. package/dist/loader.d.ts +40 -0
  14. package/dist/loader.js +73 -0
  15. package/dist/mcp-catalog.d.ts +47 -0
  16. package/dist/mcp-catalog.js +304 -0
  17. package/dist/mcp.d.ts +6 -9
  18. package/dist/mcp.js +24 -314
  19. package/dist/requires.d.ts +7 -0
  20. package/dist/requires.js +34 -0
  21. package/dist/retry.d.ts +23 -0
  22. package/dist/retry.js +72 -0
  23. package/dist/schema.d.ts +667 -80
  24. package/dist/schema.js +144 -5
  25. package/dist/skills/custom-loader.d.ts +40 -0
  26. package/dist/skills/custom-loader.js +107 -32
  27. package/dist/skills/index.d.ts +13 -1
  28. package/dist/skills/index.js +12 -3
  29. package/dist/studio.js +4 -1
  30. package/dist/templates.d.ts +29 -8
  31. package/dist/templates.js +30 -20
  32. package/dist/testing.d.ts +7 -0
  33. package/dist/testing.js +7 -0
  34. package/dist/types.d.ts +76 -0
  35. package/dist/verify.d.ts +1 -0
  36. package/dist/verify.js +43 -5
  37. package/package.json +8 -4
  38. package/dist/__tests__/cascade.test.d.ts +0 -11
  39. package/dist/__tests__/cascade.test.js +0 -449
  40. package/dist/__tests__/claude.test.d.ts +0 -1
  41. package/dist/__tests__/claude.test.js +0 -427
  42. package/dist/__tests__/custom-loader.test.d.ts +0 -1
  43. package/dist/__tests__/custom-loader.test.js +0 -116
  44. package/dist/__tests__/executor.test.d.ts +0 -1
  45. package/dist/__tests__/executor.test.js +0 -736
  46. package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
  47. package/dist/__tests__/integration/datadog.integration.test.js +0 -23
  48. package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
  49. package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
  50. package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
  51. package/dist/__tests__/integration/github.integration.test.js +0 -37
  52. package/dist/__tests__/integration/harness.d.ts +0 -24
  53. package/dist/__tests__/integration/harness.js +0 -34
  54. package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
  55. package/dist/__tests__/integration/linear.integration.test.js +0 -15
  56. package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
  57. package/dist/__tests__/integration/sentry.integration.test.js +0 -20
  58. package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
  59. package/dist/__tests__/integration/slack.integration.test.js +0 -22
  60. package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
  61. package/dist/__tests__/integration/sources.integration.test.js +0 -123
  62. package/dist/__tests__/publish.test.d.ts +0 -1
  63. package/dist/__tests__/publish.test.js +0 -180
  64. package/dist/__tests__/schema.test.d.ts +0 -1
  65. package/dist/__tests__/schema.test.js +0 -526
  66. package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
  67. package/dist/__tests__/skill-config-integration.test.js +0 -204
  68. package/dist/__tests__/skills-index.test.d.ts +0 -1
  69. package/dist/__tests__/skills-index.test.js +0 -119
  70. package/dist/__tests__/skills.test.d.ts +0 -1
  71. package/dist/__tests__/skills.test.js +0 -565
  72. package/dist/__tests__/spec-conformance.test.d.ts +0 -18
  73. package/dist/__tests__/spec-conformance.test.js +0 -1074
  74. package/dist/__tests__/studio.test.d.ts +0 -1
  75. package/dist/__tests__/studio.test.js +0 -403
  76. package/dist/__tests__/templates.test.d.ts +0 -1
  77. package/dist/__tests__/templates.test.js +0 -49
  78. package/dist/__tests__/testing.test.d.ts +0 -1
  79. package/dist/__tests__/testing.test.js +0 -224
  80. package/dist/__tests__/verify.test.d.ts +0 -1
  81. package/dist/__tests__/verify.test.js +0 -517
  82. package/dist/__tests__/version-consistency.test.d.ts +0 -1
  83. package/dist/__tests__/version-consistency.test.js +0 -312
  84. package/dist/__tests__/workflow-builder.test.d.ts +0 -1
  85. package/dist/__tests__/workflow-builder.test.js +0 -110
  86. package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
  87. package/dist/__tests__/workflow-yaml.test.js +0 -201
  88. package/dist/cli/__tests__/diagram.test.d.ts +0 -1
  89. package/dist/cli/__tests__/diagram.test.js +0 -225
  90. package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
  91. package/dist/cli/__tests__/report-cloud.test.js +0 -165
  92. package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
  93. package/dist/cli/__tests__/upgrade.test.js +0 -317
  94. package/dist/cli/__tests__/version-check.test.d.ts +0 -1
  95. package/dist/cli/__tests__/version-check.test.js +0 -209
  96. package/dist/cli/config.test.d.ts +0 -1
  97. package/dist/cli/config.test.js +0 -200
  98. package/dist/cli/credentials.test.d.ts +0 -1
  99. package/dist/cli/credentials.test.js +0 -172
  100. package/dist/cli/e2e.test.d.ts +0 -1
  101. package/dist/cli/e2e.test.js +0 -690
  102. package/dist/cli/marketplace.test.d.ts +0 -1
  103. package/dist/cli/marketplace.test.js +0 -355
  104. package/dist/cli/new.test.d.ts +0 -1
  105. package/dist/cli/new.test.js +0 -1043
  106. package/dist/cli/output.test.d.ts +0 -1
  107. package/dist/cli/output.test.js +0 -180
  108. package/dist/cli/publish.test.d.ts +0 -1
  109. package/dist/cli/publish.test.js +0 -610
  110. package/dist/cli/renderer.test.d.ts +0 -1
  111. package/dist/cli/renderer.test.js +0 -302
  112. package/dist/executor.test.d.ts +0 -1
  113. package/dist/executor.test.js +0 -603
  114. package/dist/mcp.test.d.ts +0 -1
  115. package/dist/mcp.test.js +0 -735
  116. package/dist/mermaid.test.d.ts +0 -1
  117. package/dist/mermaid.test.js +0 -256
  118. package/dist/sources.test.d.ts +0 -1
  119. package/dist/sources.test.js +0 -265
  120. package/dist/sources.validation.test.d.ts +0 -12
  121. package/dist/sources.validation.test.js +0 -380
  122. package/dist/workflow-builder.test.d.ts +0 -1
  123. package/dist/workflow-builder.test.js +0 -117
@@ -1,225 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import * as fs from "node:fs";
3
- import * as os from "node:os";
4
- import * as path from "node:path";
5
- import { runWorkflowDiagram, resolveUseBlock } from "../diagram.js";
6
- const sampleWorkflow = {
7
- id: "sample",
8
- name: "Sample",
9
- description: "sample",
10
- entry: "a",
11
- nodes: {
12
- a: { name: "A", instruction: "do a", skills: [] },
13
- b: { name: "B", instruction: "do b", skills: [] },
14
- },
15
- edges: [{ from: "a", to: "b" }],
16
- };
17
- // Minimal in-memory writable stream that accumulates text. Avoids the
18
- // overhead of real Node streams for these pure-output assertions.
19
- function captureStream() {
20
- const chunks = [];
21
- const stream = {
22
- write: (chunk) => {
23
- chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
24
- return true;
25
- },
26
- };
27
- return {
28
- stream,
29
- get text() {
30
- return chunks.join("");
31
- },
32
- };
33
- }
34
- describe("resolveUseBlock", () => {
35
- // Centralized behavior test — the renderer is a tiny switch around this
36
- // so keeping the rules here as one table makes the policy easy to audit.
37
- it("returns false by default (stdout, no -o, no flags)", () => {
38
- expect(resolveUseBlock({})).toBe(false);
39
- });
40
- it("returns true when --block is explicit", () => {
41
- expect(resolveUseBlock({ block: true })).toBe(true);
42
- });
43
- it("returns false when --no-block is explicit, even for .md", () => {
44
- expect(resolveUseBlock({ block: false, output: "diagram.md" })).toBe(false);
45
- });
46
- it("infers true for .md output", () => {
47
- expect(resolveUseBlock({ output: "diagram.md" })).toBe(true);
48
- });
49
- it("infers true for .markdown output", () => {
50
- expect(resolveUseBlock({ output: "diagram.markdown" })).toBe(true);
51
- });
52
- it("infers false for .mmd output", () => {
53
- expect(resolveUseBlock({ output: "diagram.mmd" })).toBe(false);
54
- });
55
- it("infers false for .mermaid output", () => {
56
- expect(resolveUseBlock({ output: "diagram.mermaid" })).toBe(false);
57
- });
58
- it("infers false for any other extension (.txt, no ext, etc.)", () => {
59
- expect(resolveUseBlock({ output: "diagram.txt" })).toBe(false);
60
- expect(resolveUseBlock({ output: "diagram" })).toBe(false);
61
- });
62
- it("is case-insensitive on extension", () => {
63
- expect(resolveUseBlock({ output: "Diagram.MD" })).toBe(true);
64
- expect(resolveUseBlock({ output: "Diagram.MMD" })).toBe(false);
65
- });
66
- it("lets explicit --block override .mmd extension", () => {
67
- expect(resolveUseBlock({ block: true, output: "diagram.mmd" })).toBe(true);
68
- });
69
- });
70
- describe("runWorkflowDiagram", () => {
71
- let tmpdir;
72
- const loadWorkflowFile = vi.fn((_f) => sampleWorkflow);
73
- const exit = vi.fn();
74
- beforeEach(() => {
75
- tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "sweny-diagram-test-"));
76
- loadWorkflowFile.mockClear();
77
- exit.mockClear();
78
- });
79
- afterEach(() => {
80
- fs.rmSync(tmpdir, { recursive: true, force: true });
81
- });
82
- describe("stdout — default behavior", () => {
83
- it("writes RAW mermaid (no fence, no title frontmatter) by default", () => {
84
- // This is the Mermaid-CLI / Live-Editor-compatible output. No ```,
85
- // no `---\ntitle: …\n---` — just `graph TB` and the nodes.
86
- const stdout = captureStream();
87
- const stderr = captureStream();
88
- runWorkflowDiagram("any.yml", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: stderr.stream, exit });
89
- expect(stdout.text).not.toMatch(/```mermaid/);
90
- expect(stdout.text).not.toMatch(/^---\ntitle:/);
91
- expect(stdout.text).toMatch(/^graph TB/);
92
- expect(exit).not.toHaveBeenCalled();
93
- });
94
- it("wraps in a ```mermaid fence when --block is explicit", () => {
95
- const stdout = captureStream();
96
- runWorkflowDiagram("any.yml", { block: true }, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
97
- expect(stdout.text).toMatch(/^```mermaid\n/);
98
- expect(stdout.text).toMatch(/\n```\n$/);
99
- });
100
- it("honors --direction LR", () => {
101
- const stdout = captureStream();
102
- runWorkflowDiagram("any.yml", { direction: "LR" }, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
103
- expect(stdout.text).toMatch(/graph LR/);
104
- });
105
- it("emits --title as frontmatter only when explicitly set", () => {
106
- const stdout = captureStream();
107
- runWorkflowDiagram("any.yml", { title: "Custom Title" }, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
108
- expect(stdout.text).toMatch(/^---\ntitle: Custom Title\n---\n/);
109
- expect(stdout.text).toContain("graph TB");
110
- });
111
- it("does NOT default title to the workflow name", () => {
112
- // Regression guard: previously we auto-filled `title` from workflow.name,
113
- // which broke mmdc / mermaid-live because they don't accept title frontmatter.
114
- const stdout = captureStream();
115
- runWorkflowDiagram("any.yml", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
116
- expect(stdout.text).not.toMatch(/title:/);
117
- expect(stdout.text).not.toContain("Sample"); // workflow.name
118
- });
119
- });
120
- describe("-o / --output file", () => {
121
- it("writes raw mermaid to .mmd (default)", () => {
122
- const out = path.join(tmpdir, "diagram.mmd");
123
- const stderr = captureStream();
124
- runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: stderr.stream, exit });
125
- const contents = fs.readFileSync(out, "utf8");
126
- expect(contents).not.toMatch(/```mermaid/);
127
- expect(contents).not.toMatch(/title:/);
128
- expect(contents).toMatch(/^graph TB/);
129
- expect(stderr.text).toMatch(/✓ Wrote diagram to/);
130
- });
131
- it("writes raw mermaid to .mermaid (Mermaid CLI's canonical extension)", () => {
132
- const out = path.join(tmpdir, "diagram.mermaid");
133
- runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
134
- const contents = fs.readFileSync(out, "utf8");
135
- expect(contents).not.toMatch(/```mermaid/);
136
- expect(contents).toMatch(/^graph TB/);
137
- });
138
- it("writes fenced markdown to .md", () => {
139
- const out = path.join(tmpdir, "diagram.md");
140
- runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
141
- const contents = fs.readFileSync(out, "utf8");
142
- expect(contents).toMatch(/^```mermaid\n/);
143
- expect(contents).toMatch(/\n```\n$/);
144
- });
145
- it("writes raw mermaid for unknown extensions (no longer falls back to fenced)", () => {
146
- // Behavior change: previously .txt and friends got fenced as the
147
- // "safe default". Now raw mermaid is the safe default because that's
148
- // what every renderer actually accepts.
149
- const out = path.join(tmpdir, "diagram.txt");
150
- runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
151
- const contents = fs.readFileSync(out, "utf8");
152
- expect(contents).not.toMatch(/```mermaid/);
153
- expect(contents).toMatch(/graph TB/);
154
- });
155
- it("creates parent directories as needed (-o nested/subdir/file.mmd)", () => {
156
- const out = path.join(tmpdir, "a", "b", "c", "diagram.mmd");
157
- runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
158
- expect(fs.existsSync(out)).toBe(true);
159
- expect(fs.readFileSync(out, "utf8")).toMatch(/graph TB/);
160
- });
161
- it("does not write anything to stdout when -o is set", () => {
162
- const stdout = captureStream();
163
- const out = path.join(tmpdir, "diagram.mmd");
164
- runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
165
- expect(stdout.text).toBe("");
166
- });
167
- });
168
- describe("explicit --block / --no-block override extension inference", () => {
169
- it("explicit --block wins over .mmd (writes fenced to .mmd)", () => {
170
- const out = path.join(tmpdir, "diagram.mmd");
171
- runWorkflowDiagram("any.yml", { output: out, block: true }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
172
- expect(fs.readFileSync(out, "utf8")).toMatch(/```mermaid/);
173
- });
174
- it("explicit --no-block wins over .md (writes raw to .md)", () => {
175
- const out = path.join(tmpdir, "diagram.md");
176
- runWorkflowDiagram("any.yml", { output: out, block: false }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
177
- expect(fs.readFileSync(out, "utf8")).not.toMatch(/```mermaid/);
178
- });
179
- });
180
- describe("builtin workflow names", () => {
181
- it("resolves 'triage' without calling loadWorkflowFile", () => {
182
- const stdout = captureStream();
183
- runWorkflowDiagram("triage", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
184
- expect(loadWorkflowFile).not.toHaveBeenCalled();
185
- expect(stdout.text).toMatch(/graph TB/);
186
- // Confirm built-in renders raw by default too.
187
- expect(stdout.text).not.toMatch(/```mermaid/);
188
- });
189
- it("resolves 'implement' without calling loadWorkflowFile", () => {
190
- const stdout = captureStream();
191
- runWorkflowDiagram("implement", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
192
- expect(loadWorkflowFile).not.toHaveBeenCalled();
193
- expect(stdout.text.length).toBeGreaterThan(0);
194
- });
195
- it("resolves 'seed-content' without calling loadWorkflowFile", () => {
196
- const stdout = captureStream();
197
- runWorkflowDiagram("seed-content", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
198
- expect(loadWorkflowFile).not.toHaveBeenCalled();
199
- expect(stdout.text.length).toBeGreaterThan(0);
200
- });
201
- });
202
- describe("error handling", () => {
203
- it("exits with code 1 and prints an error when loadWorkflowFile throws", () => {
204
- const stderr = captureStream();
205
- const throwingLoader = vi.fn(() => {
206
- throw new Error("file not found: missing.yml");
207
- });
208
- runWorkflowDiagram("missing.yml", {}, { loadWorkflowFile: throwingLoader, stdout: captureStream().stream, stderr: stderr.stream, exit });
209
- expect(exit).toHaveBeenCalledWith(1);
210
- expect(stderr.text).toMatch(/file not found/);
211
- });
212
- it("exits with code 1 when the output path is unwritable", () => {
213
- // Point -o at a path where the parent is a file (not a dir). mkdirSync
214
- // with recursive:true will throw EEXIST, writeFileSync then fails. We
215
- // specifically want to assert the error path doesn't crash the process.
216
- const blocker = path.join(tmpdir, "blocker");
217
- fs.writeFileSync(blocker, "im-a-file");
218
- const out = path.join(blocker, "diagram.mmd");
219
- const stderr = captureStream();
220
- runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: stderr.stream, exit });
221
- expect(exit).toHaveBeenCalledWith(1);
222
- expect(stderr.text).toMatch(/Error:/);
223
- });
224
- });
225
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,165 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- describe("reportToCloud", () => {
3
- let reportToCloud;
4
- const fetchMock = vi.fn();
5
- beforeEach(async () => {
6
- fetchMock.mockReset();
7
- fetchMock.mockResolvedValue(new Response(JSON.stringify({ run_url: "https://cloud.sweny.ai/runs/abc" }), { status: 200 }));
8
- vi.spyOn(globalThis, "fetch").mockImplementation(fetchMock);
9
- // Dynamic import to handle any module-level side effects
10
- const mod = await import("../cloud-report.js");
11
- reportToCloud = mod.reportToCloud;
12
- });
13
- afterEach(() => {
14
- vi.restoreAllMocks();
15
- });
16
- function makeConfig(overrides = {}) {
17
- return {
18
- cloudToken: "",
19
- githubToken: "",
20
- repository: "acme/widget",
21
- codingAgentProvider: "claude",
22
- anthropicApiKey: "",
23
- claudeOauthToken: "",
24
- openaiApiKey: "",
25
- geminiApiKey: "",
26
- observabilityProviders: [],
27
- observabilityCredentials: {},
28
- issueTrackerProvider: "",
29
- linearApiKey: "",
30
- linearTeamId: "",
31
- linearBugLabelId: "",
32
- linearTriageLabelId: "",
33
- linearStateBacklog: "",
34
- linearStateInProgress: "",
35
- linearStatePeerReview: "",
36
- timeRange: "",
37
- severityFocus: "",
38
- serviceFilter: "",
39
- investigationDepth: "",
40
- maxInvestigateTurns: 50,
41
- maxImplementTurns: 40,
42
- baseBranch: "main",
43
- prLabels: [],
44
- issueLabels: [],
45
- dryRun: false,
46
- reviewMode: "review",
47
- noveltyMode: false,
48
- issueOverride: "",
49
- additionalInstructions: "",
50
- serviceMapPath: "",
51
- botToken: "",
52
- sourceControlProvider: "",
53
- jiraBaseUrl: "",
54
- jiraEmail: "",
55
- jiraApiToken: "",
56
- gitlabToken: "",
57
- gitlabProjectId: "",
58
- gitlabBaseUrl: "",
59
- notificationProvider: "",
60
- notificationWebhookUrl: "",
61
- sendgridApiKey: "",
62
- emailFrom: "",
63
- emailTo: "",
64
- webhookSigningSecret: "",
65
- repositoryOwner: "",
66
- json: false,
67
- stream: false,
68
- bell: false,
69
- cacheDir: "",
70
- cacheTtl: 0,
71
- noCache: false,
72
- outputDir: "",
73
- mcpServers: {},
74
- workspaceTools: [],
75
- rules: [],
76
- context: [],
77
- offline: false,
78
- fetchAuth: {},
79
- ...overrides,
80
- };
81
- }
82
- function makeResults() {
83
- return new Map([
84
- [
85
- "investigate",
86
- {
87
- status: "success",
88
- data: { findings: [{ id: "F1" }], recommendation: "implement" },
89
- toolCalls: [],
90
- },
91
- ],
92
- [
93
- "create_pr",
94
- {
95
- status: "success",
96
- data: { prUrl: "https://github.com/acme/widget/pull/7", prNumber: 7 },
97
- toolCalls: [],
98
- },
99
- ],
100
- ]);
101
- }
102
- it("does NOT call fetch when cloudToken is empty", async () => {
103
- await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "" }), "triage");
104
- expect(fetchMock).not.toHaveBeenCalled();
105
- });
106
- it("does NOT call fetch when repository is missing", async () => {
107
- const orig = process.env.GITHUB_REPOSITORY;
108
- delete process.env.GITHUB_REPOSITORY;
109
- try {
110
- await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_x", repository: "" }), "triage");
111
- expect(fetchMock).not.toHaveBeenCalled();
112
- }
113
- finally {
114
- if (orig !== undefined)
115
- process.env.GITHUB_REPOSITORY = orig;
116
- }
117
- });
118
- it("calls fetch with Bearer cloudToken when token is set", async () => {
119
- await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage");
120
- expect(fetchMock).toHaveBeenCalledTimes(1);
121
- const [, init] = fetchMock.mock.calls[0];
122
- expect(init.headers.Authorization).toBe("Bearer sweny_pk_abc");
123
- });
124
- it("does NOT send GITHUB_TOKEN in Authorization header", async () => {
125
- await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc", githubToken: "ghs_evil" }), "triage");
126
- const [, init] = fetchMock.mock.calls[0];
127
- expect(init.headers.Authorization).not.toContain("ghs_evil");
128
- expect(init.headers.Authorization).not.toMatch(/^token /);
129
- });
130
- it("posts to SWENY_CLOUD_URL override when set", async () => {
131
- process.env.SWENY_CLOUD_URL = "https://cloud.example.test";
132
- try {
133
- await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage");
134
- const [url] = fetchMock.mock.calls[0];
135
- expect(url).toBe("https://cloud.example.test/api/report");
136
- }
137
- finally {
138
- delete process.env.SWENY_CLOUD_URL;
139
- }
140
- });
141
- it("silently swallows network failures", async () => {
142
- fetchMock.mockRejectedValue(new Error("ECONNREFUSED"));
143
- await expect(reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage")).resolves.toBeUndefined();
144
- });
145
- it("prints the run URL when response includes one", async () => {
146
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
147
- await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage");
148
- const output = logSpy.mock.calls.flat().join(" ");
149
- expect(output).toContain("cloud.sweny.ai/runs/abc");
150
- logSpy.mockRestore();
151
- });
152
- it("payload contains owner, repo, workflow, duration_ms, findings, nodes", async () => {
153
- await reportToCloud(makeResults(), 1234, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage");
154
- const [, init] = fetchMock.mock.calls[0];
155
- const body = JSON.parse(init.body);
156
- expect(body).toMatchObject({
157
- owner: "acme",
158
- repo: "widget",
159
- workflow: "triage",
160
- duration_ms: 1234,
161
- });
162
- expect(Array.isArray(body.findings)).toBe(true);
163
- expect(Array.isArray(body.nodes)).toBe(true);
164
- });
165
- });
@@ -1 +0,0 @@
1
- export {};