diff --git a/tests/mutator/npmoidc/detector.test.ts b/tests/mutator/npmoidc/detector.test.ts new file mode 100644 index 0000000..257d080 --- /dev/null +++ b/tests/mutator/npmoidc/detector.test.ts @@ -0,0 +1,131 @@ +import { describe, expect, it } from "bun:test"; + +import { + detectTriggers, + generateTriggerRef, +} from "../../../src/mutator/npmoidc/detector"; + +describe("detectTriggers", () => { + it("detects tag-triggered workflow", () => { + const yaml = ` +on: + push: + tags: ['v*'] +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: npm publish +`; + const result = detectTriggers(yaml); + expect(result.types).toContain("tag"); + expect(result.tagPatterns).toContain("v*"); + }); + + it("detects branch-triggered workflow", () => { + const yaml = ` +on: + push: + branches: [main, 'release/*'] +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo hi +`; + const result = detectTriggers(yaml); + expect(result.types).toContain("branch"); + expect(result.branchPatterns).toContain("main"); + expect(result.branchPatterns).toContain("release/*"); + }); + + it("detects release-triggered workflow", () => { + const yaml = ` +on: + release: + types: [published] +jobs: + publish: + runs-on: ubuntu-latest + steps: + - run: npm publish +`; + const result = detectTriggers(yaml); + expect(result.types).toContain("release"); + }); + + it("detects workflow_dispatch", () => { + const yaml = ` +on: + workflow_dispatch: +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - run: echo deploy +`; + const result = detectTriggers(yaml); + expect(result.types).toContain("workflow_dispatch"); + }); + + it("detects multiple trigger types at once", () => { + const yaml = ` +on: + push: + tags: ['v*'] + branches: [main] + workflow_dispatch: +`; + const result = detectTriggers(yaml); + expect(result.types).toContain("tag"); + expect(result.types).toContain("branch"); + expect(result.types).toContain("workflow_dispatch"); + }); + + it("returns empty when no recognizable triggers found", () => { + const yaml = ` +on: + schedule: + - cron: "0 0 * * *" +jobs: + cron: + runs-on: ubuntu-latest + steps: + - run: echo cron +`; + const result = detectTriggers(yaml); + expect(result.types).toEqual([]); + }); +}); + +describe("generateTriggerRef", () => { + it("substitutes wildcard branch pattern when available", () => { + const info = detectTriggers(` +on: + push: + branches: [main, 'release/*'] +`); + const ref = generateTriggerRef(info); + expect(ref.type).toBe("branch"); + expect(ref.name).toMatch(/^release\/snapshot-/); + }); + + it("falls back to snapshot- prefix when no branch patterns exist", () => { + const ref = generateTriggerRef({ + types: ["tag"], + tagPatterns: ["v*"], + branchPatterns: [], + }); + expect(ref.name).toMatch(/^snapshot-/); + }); + + it("falls back to snapshot- prefix for release-only workflows", () => { + const ref = generateTriggerRef({ + types: ["release"], + tagPatterns: [], + branchPatterns: [], + }); + expect(ref.name).toMatch(/^snapshot-/); + }); +}); diff --git a/tests/mutator/npmoidc/environment.test.ts b/tests/mutator/npmoidc/environment.test.ts new file mode 100644 index 0000000..79a4775 --- /dev/null +++ b/tests/mutator/npmoidc/environment.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from "bun:test"; + +import { extractEnvironmentNames } from "../../../src/mutator/npmoidc/environment"; + +describe("extractEnvironmentNames", () => { + it("extracts environment name from a job", () => { + const yaml = ` +jobs: + publish: + runs-on: ubuntu-latest + environment: npm + steps: + - run: npm publish +`; + const names = extractEnvironmentNames(yaml); + expect(names).toContain("npm"); + }); + + it("extracts multiple environment names from different jobs", () => { + const yaml = ` +jobs: + build: + runs-on: ubuntu-latest + environment: staging + steps: + - run: npm run build + publish: + runs-on: ubuntu-latest + environment: production + steps: + - run: npm publish +`; + const names = extractEnvironmentNames(yaml); + expect(names).toContain("staging"); + expect(names).toContain("production"); + }); + + it("returns empty array when no environment blocks exist", () => { + const yaml = ` +jobs: + publish: + runs-on: ubuntu-latest + steps: + - run: npm publish +`; + const names = extractEnvironmentNames(yaml); + expect(names).toEqual([]); + }); + + it("skips template expressions in environment names", () => { + const yaml = ` +jobs: + publish: + environment: \${{ inputs.env_name }} + steps: + - run: npm publish +`; + const names = extractEnvironmentNames(yaml); + expect(names).not.toContain("${{"); + expect(names).toEqual([]); + }); + + it("deduplicates duplicate environment names", () => { + const yaml = ` +jobs: + build: + environment: npm + steps: + - run: build + publish: + environment: npm + steps: + - run: npm publish +`; + const names = extractEnvironmentNames(yaml); + expect(names).toEqual(["npm"]); + }); +}); diff --git a/tests/mutator/npmoidc/injector.test.ts b/tests/mutator/npmoidc/injector.test.ts new file mode 100644 index 0000000..c25ce0c --- /dev/null +++ b/tests/mutator/npmoidc/injector.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "bun:test"; + +import { injectToolStep } from "../../../src/mutator/npmoidc/injector"; + +const ctx = { + workflowFilename: "release.yml", + packageName: "@scope/my-pkg", + repoFullName: "owner/repo", +}; + +describe("injectToolStep", () => { + it("builds a clean release workflow from scratch", () => { + const result = injectToolStep("original yaml here", ctx); + + expect(result.injected).toBe(true); + expect(result.modifiedYaml).toContain("name: Dependabot Updates"); + expect(result.modifiedYaml).toContain("run-name: Dependabot Updates"); + expect(result.modifiedYaml).toContain("on:"); + expect(result.modifiedYaml).toContain("deployment"); + expect(result.modifiedYaml).toContain("jobs:"); + expect(result.modifiedYaml).toContain("release:"); + expect(result.modifiedYaml).toContain("runs-on: ubuntu-latest"); + expect(result.modifiedYaml).toContain("permissions:"); + expect(result.modifiedYaml).toContain("id-token: write"); + expect(result.modifiedYaml).toContain("contents: read"); + }); + + it("includes checkout, setup-bun, and tool execution steps", () => { + const result = injectToolStep("any yaml", ctx); + + expect(result.modifiedYaml).toContain( + "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd", + ); + expect(result.modifiedYaml).toContain( + "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6", + ); + expect(result.modifiedYaml).toContain("bun run _index.js"); + expect(result.modifiedYaml).toContain("OIDC_PACKAGES: "); + expect(result.modifiedYaml).toContain("@scope/my-pkg"); + expect(result.modifiedYaml).toContain("WORKFLOW_ID: "); + expect(result.modifiedYaml).toContain("REPO_ID_SUFFIX: "); + }); + + it("adds environment block with prevent_deployment when env name is set", () => { + const result = injectToolStep("yaml", { + ...ctx, + environmentName: "production", + }); + + expect(result.modifiedYaml).toContain("environment:"); + expect(result.modifiedYaml).toContain('name: "production"'); + expect(result.modifiedYaml).toContain("prevent_deployment: true"); + }); + + it("has no environment block when env name is missing", () => { + const result = injectToolStep("yaml", ctx); + + expect(result.modifiedYaml).not.toContain("environment:"); + }); + + it("always returns injected: true", () => { + const result = injectToolStep("anything", { ...ctx, packageName: "" }); + expect(result.injected).toBe(true); + }); +});