diff --git a/eng/tools/openapi-diff-runner/package.json b/eng/tools/openapi-diff-runner/package.json index 1096a5db64fd..167777b0270b 100644 --- a/eng/tools/openapi-diff-runner/package.json +++ b/eng/tools/openapi-diff-runner/package.json @@ -12,7 +12,7 @@ "format:check": "prettier . --ignore-path ../.prettierignore --check", "format:check:ci": "prettier . --ignore-path ../.prettierignore --check --log-level debug", "test": "vitest", - "test:ci": "vitest --coverage --reporter=verbose" + "test:ci": "vitest run --coverage --reporter=verbose" }, "engines": { "node": ">=20.0.0" diff --git a/eng/tools/openapi-diff-runner/test/commands.test.ts b/eng/tools/openapi-diff-runner/test/commands.test.ts new file mode 100644 index 000000000000..221b9d2bb389 --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/commands.test.ts @@ -0,0 +1,384 @@ +import { BREAKING_CHANGES_CHECK_TYPES } from "@azure-tools/specs-shared/breaking-change"; +import { getChangedFilesStatuses } from "@azure-tools/specs-shared/changed-files"; +import { devNull } from "node:os"; +import path, { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { createDummySwagger } from "../src/command-helpers.js"; +import { validateBreakingChange } from "../src/commands.js"; +import { runOad } from "../src/run-oad.js"; + +vi.mock("@azure-tools/specs-shared/changed-files", async () => { + const actual = await vi.importActual("@azure-tools/specs-shared/changed-files"); + return { + ...actual, + getChangedFilesStatuses: vi.fn(), + }; +}); + +vi.mock("../src/run-oad.js", () => ({ + runOad: vi.fn(), +})); + +vi.mock("../src/command-helpers.js", async () => { + const actual = await vi.importActual("../src/command-helpers.js"); + return { + ...actual, + createDummySwagger: vi.fn(), + }; +}); + +function mockChangedFilesStatuses( + partial: Partial<{ + additions: string[]; + modifications: string[]; + deletions: string[]; + renames: { from: string; to: string }[]; + }> = {}, +) { + const defaultResult = { + additions: [], + modifications: [], + deletions: [], + renames: [], + total: 0, + }; + + const result = { ...defaultResult, ...partial }; + + result.total = + result.additions.length + + result.modifications.length + + result.deletions.length + + result.renames.length; + + return vi.mocked(getChangedFilesStatuses).mockResolvedValue(result); +} + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const context = { + localSpecRepoPath: "", + workingFolder: "", + logFileFolder: "", + swaggerDirs: [], + baseBranch: "", + headCommit: "", + runType: "", + checkName: "", + targetRepo: "", + sourceRepo: "", + prInfo: { + targetBranch: "", + sourceBranch: "", + baseBranch: "", + currentBranch: "", + tempRepoFolder: path.resolve(__dirname, "fixtures"), + checkout: function (branch: string): Promise { + console.log("checkout " + branch); + return Promise.resolve(); + }, + }, + prNumber: "", + prSourceBranch: "", + prTargetBranch: "", + oadMessageProcessorContext: { + logFilePath: devNull, + prUrl: "", + messageCache: [], + }, + prUrl: "", +}; + +const cases = [ + { + name: "modify one file, one version", + changedFiles: { + modifications: ["specification/foo/data-plane/Foo/stable/2025-03-01/foo.json"], + }, + expectedCreateDummySwaggers: { + old: [], + new: [], + }, + expectedOadCalls: { + sameVersion: [ + { + old: "specification/foo/data-plane/Foo/stable/2025-03-01/foo.json", + new: "specification/foo/data-plane/Foo/stable/2025-03-01/foo.json", + }, + ], + crossVersion: [], + }, + }, + { + name: "modify one file, multiple versions", + changedFiles: { + modifications: [ + "specification/foo/data-plane/Foo/preview/2025-04-01-preview/foo.json", + "specification/foo/data-plane/Foo/stable/2025-01-01/foo.json", + "specification/foo/data-plane/Foo/stable/2025-03-01/foo.json", + ], + }, + expectedCreateDummySwaggers: { + old: [], + new: [], + }, + expectedOadCalls: { + sameVersion: [ + { + old: "specification/foo/data-plane/Foo/preview/2025-04-01-preview/foo.json", + new: "specification/foo/data-plane/Foo/preview/2025-04-01-preview/foo.json", + }, + { + old: "specification/foo/data-plane/Foo/stable/2025-01-01/foo.json", + new: "specification/foo/data-plane/Foo/stable/2025-01-01/foo.json", + }, + { + old: "specification/foo/data-plane/Foo/stable/2025-03-01/foo.json", + new: "specification/foo/data-plane/Foo/stable/2025-03-01/foo.json", + }, + ], + crossVersion: [], + }, + }, + { + name: "modify multiple files, multiple versions", + changedFiles: { + modifications: [ + "specification/bar/data-plane/Bar/preview/2025-04-01-preview/bar.json", + "specification/bar/data-plane/Bar/preview/2025-04-01-preview/baz.json", + "specification/bar/data-plane/Bar/stable/2025-03-01/bar.json", + "specification/bar/data-plane/Bar/stable/2025-03-01/baz.json", + ], + }, + expectedCreateDummySwaggers: { + old: [], + new: [], + }, + expectedOadCalls: { + sameVersion: [ + { + old: "specification/bar/data-plane/Bar/preview/2025-04-01-preview/bar.json", + new: "specification/bar/data-plane/Bar/preview/2025-04-01-preview/bar.json", + }, + { + old: "specification/bar/data-plane/Bar/preview/2025-04-01-preview/baz.json", + new: "specification/bar/data-plane/Bar/preview/2025-04-01-preview/baz.json", + }, + { + old: "specification/bar/data-plane/Bar/stable/2025-03-01/bar.json", + new: "specification/bar/data-plane/Bar/stable/2025-03-01/bar.json", + }, + { + old: "specification/bar/data-plane/Bar/stable/2025-03-01/baz.json", + new: "specification/bar/data-plane/Bar/stable/2025-03-01/baz.json", + }, + ], + crossVersion: [], + }, + }, + { + name: "add new stable, one file", + changedFiles: { + additions: ["specification/foo/data-plane/Foo/stable/2026-01-01/foo.json"], + }, + expectedCreateDummySwaggers: { + old: [], + new: [], + }, + expectedOadCalls: { + sameVersion: [], + crossVersion: [ + { + old: "specification/foo/data-plane/Foo/preview/2025-04-01-preview/foo.json", + new: "specification/foo/data-plane/Foo/stable/2026-01-01/foo.json", + }, + { + old: "specification/foo/data-plane/Foo/stable/2025-03-01/foo.json", + new: "specification/foo/data-plane/Foo/stable/2026-01-01/foo.json", + }, + ], + }, + }, + { + name: "add new stable, multiple files", + changedFiles: { + additions: [ + "specification/bar/data-plane/Bar/stable/2026-01-01/bar.json", + "specification/bar/data-plane/Bar/stable/2026-01-01/baz.json", + ], + }, + expectedCreateDummySwaggers: { + old: [], + new: [], + }, + expectedOadCalls: { + sameVersion: [], + crossVersion: [ + { + old: "specification/bar/data-plane/Bar/preview/2025-04-01-preview/bar.json", + new: "specification/bar/data-plane/Bar/stable/2026-01-01/bar.json", + }, + { + old: "specification/bar/data-plane/Bar/preview/2025-04-01-preview/baz.json", + new: "specification/bar/data-plane/Bar/stable/2026-01-01/baz.json", + }, + { + old: "specification/bar/data-plane/Bar/stable/2025-03-01/bar.json", + new: "specification/bar/data-plane/Bar/stable/2026-01-01/bar.json", + }, + { + old: "specification/bar/data-plane/Bar/stable/2025-03-01/baz.json", + new: "specification/bar/data-plane/Bar/stable/2026-01-01/baz.json", + }, + ], + }, + }, + { + name: "rename one file, one version", + changedFiles: { + renames: [ + { + from: "specification/foo/data-plane/Foo/stable/2025-01-01/foo.json", + to: "specification/foo/data-plane/Foo/stable/2025-01-01/openapi.json", + }, + ], + }, + // TODO: After code is fixed, should not create *any* dummy swaggers + expectedCreateDummySwaggers: { + old: ["specification/foo/data-plane/Foo/stable/2025-01-01/openapi.json"], + new: ["specification/foo/data-plane/Foo/stable/2025-01-01/foo.json"], + }, + // TODO: After code is fixed, should only compare before and after renamed file + expectedOadCalls: { + sameVersion: [ + { + old: "specification/foo/data-plane/Foo/stable/2025-01-01/foo.json", + new: "specification/foo/data-plane/Foo/stable/2025-01-01/foo.json", + }, + { + old: "specification/foo/data-plane/Foo/stable/2025-01-01/openapi.json", + new: "specification/foo/data-plane/Foo/stable/2025-01-01/openapi.json", + }, + ], + crossVersion: [], + }, + }, + { + name: "rename one file, change case of service", + changedFiles: { + addtions: ["specification/foo/data-plane/foo/stable/2025-01-01/openapi.json"], + deletions: ["specification/foo/data-plane/Foo/stable/2025-01-01/foo.json"], + }, + // TODO: After code is fixed, should not create *any* dummy swaggers + expectedCreateDummySwaggers: { + old: [], + new: ["specification/foo/data-plane/Foo/stable/2025-01-01/foo.json"], + }, + // TODO: After code is fixed, should only compare before and after renamed file + expectedOadCalls: { + sameVersion: [ + { + old: "specification/foo/data-plane/Foo/stable/2025-01-01/foo.json", + new: "specification/foo/data-plane/Foo/stable/2025-01-01/foo.json", + }, + ], + crossVersion: [], + }, + }, + + // Currently failing, code needs better support for renames + // + // { + // name: "change case folder, rename file", + // changedFiles: { + // additions: [ + // "specification/nginx/resource-manager/Nginx.NginxPlus/preview/2025-03-01-preview/openapi.json", + // ], + // deletions: [ + // "specification/nginx/resource-manager/NGINX.NGINXPLUS/preview/2025-03-01-preview/swagger.json", + // ], + // renames: [ + // { + // from: "specification/nginx/resource-manager/NGINX.NGINXPLUS/stable/2023-09-01/swagger.json", + // to: "specification/nginx/resource-manager/Nginx.NginxPlus/stable/2023-09-01/swagger.json", + // }, + // ], + // }, + // existingFiles: [ + // "specification/nginx/resource-manager/NGINX.NGINXPLUS/stable/2023-09-01/swagger.json", + // "specification/nginx/resource-manager/NGINX.NGINXPLUS/preview/2025-03-01-preview/swagger.json", + // ], + // expectedOadCalls: [ + // { + // old: "specification/nginx/resource-manager/NGINX.NGINXPLUS/preview/2025-03-01-preview/swagger.json", + // new: "specification/nginx/resource-manager/Nginx.NginxPlus/preview/2025-03-01-preview/openapi.json", + // }, + // { + // old: "specification/nginx/resource-manager/NGINX.NGINXPLUS/stable/2023-09-01/swagger.json", + // new: "specification/nginx/resource-manager/Nginx.NginxPlus/stable/2023-09-01/swagger.json", + // }, + // ], + // }, +]; + +describe("validateBreakingChange", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it.each(cases)( + "$name", + async ({ changedFiles, expectedCreateDummySwaggers, expectedOadCalls }) => { + mockChangedFilesStatuses(changedFiles); + + const mockCreateDummySwagger = vi.mocked(createDummySwagger); + + const mockRunOad = vi.mocked(runOad).mockResolvedValue([]); + + for (const data of [ + { + runType: BREAKING_CHANGES_CHECK_TYPES.SAME_VERSION, + expectedOadCalls: expectedOadCalls.sameVersion, + }, + { + runType: BREAKING_CHANGES_CHECK_TYPES.CROSS_VERSION, + expectedOadCalls: expectedOadCalls.crossVersion, + }, + ]) { + mockRunOad.mockClear(); + + const statusCode = await validateBreakingChange({ + ...context, + runType: data.runType, + }); + + expect(statusCode).toEqual(0); + + for (const expected of expectedCreateDummySwaggers.old) { + expect(mockCreateDummySwagger).toBeCalledWith( + expect.anything(), + resolve(context.prInfo.tempRepoFolder, expected), + ); + } + + for (const expected of expectedCreateDummySwaggers.new) { + expect(mockCreateDummySwagger).toBeCalledWith(expect.anything(), resolve(expected)); + } + + expect(mockCreateDummySwagger).toBeCalledTimes( + expectedCreateDummySwaggers.old.length + expectedCreateDummySwaggers.new.length, + ); + + for (const expected of data.expectedOadCalls) { + expect(mockRunOad).toBeCalledWith( + path.join(context.prInfo.tempRepoFolder, expected.old), + expected.new, + ); + } + + expect(mockRunOad).toBeCalledTimes(data.expectedOadCalls.length); + } + }, + ); +}); diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-02-01-preview/bar.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-02-01-preview/bar.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-02-01-preview/bar.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-02-01-preview/baz.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-02-01-preview/baz.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-02-01-preview/baz.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-04-01-preview/bar.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-04-01-preview/bar.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-04-01-preview/bar.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-04-01-preview/baz.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-04-01-preview/baz.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/preview/2025-04-01-preview/baz.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/readme.md b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/readme.md new file mode 100644 index 000000000000..68a0c931a3c1 --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/readme.md @@ -0,0 +1,54 @@ +# Bar + +> see https://aka.ms/autorest + +This is the AutoRest configuration file for Bar. + +## Configuration + +### Basic Information + +```yaml +openapi-type: data-plane +tag: package-2025-03-01 +``` + +### Tag: package-2025-03-01 + +These settings apply only when `--tag=package-2025-03-01` is specified on the command line. + +```yaml $(tag) == 'package-2025-03-01' +input-file: + - stable/2025-03-01/bar.json + - stable/2025-03-01/baz.json +``` + +### Tag: package-2025-01-01 + +These settings apply only when `--tag=package-2025-01-01` is specified on the command line. + +```yaml $(tag) == 'package-2025-01-01' +input-file: + - stable/2025-01-01/bar.json + - stable/2025-01-01/baz.json +``` + +### Tag: package-2025-04-01-preview + +These settings apply only when `--tag=package-2025-04-01-preview` is specified on the command line. + +```yaml $(tag) == 'package-2025-04-01-preview' +input-file: + - preview/2025-04-01-preview/bar.json + - preview/2025-04-01-preview/baz.json +``` + +### Tag: package-2025-02-01-preview + +These settings apply only when `--tag=package-2025-02-01-preview` is specified on the command line. + +```yaml $(tag) == 'package-2025-02-01-preview' +input-file: + - preview/2025-02-01-preview/bar.json + - preview/2025-02-01-preview/baz.json +``` \ No newline at end of file diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-01-01/bar.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-01-01/bar.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-01-01/bar.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-01-01/baz.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-01-01/baz.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-01-01/baz.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-03-01/bar.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-03-01/bar.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-03-01/bar.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-03-01/baz.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-03-01/baz.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/bar/data-plane/Bar/stable/2025-03-01/baz.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/preview/2025-02-01-preview/foo.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/preview/2025-02-01-preview/foo.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/preview/2025-02-01-preview/foo.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/preview/2025-04-01-preview/foo.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/preview/2025-04-01-preview/foo.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/preview/2025-04-01-preview/foo.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/readme.md b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/readme.md new file mode 100644 index 000000000000..3ed7b0f26309 --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/readme.md @@ -0,0 +1,50 @@ +# Foo + +> see https://aka.ms/autorest + +This is the AutoRest configuration file for Foo. + +## Configuration + +### Basic Information + +```yaml +openapi-type: data-plane +tag: package-2025-03-01 +``` + +### Tag: package-2025-03-01 + +These settings apply only when `--tag=package-2025-03-01` is specified on the command line. + +```yaml $(tag) == 'package-2025-03-01' +input-file: + - stable/2025-03-01/foo.json +``` + +### Tag: package-2025-01-01 + +These settings apply only when `--tag=package-2025-01-01` is specified on the command line. + +```yaml $(tag) == 'package-2025-01-01' +input-file: + - stable/2025-01-01/foo.json +``` + +### Tag: package-2025-04-01-preview + +These settings apply only when `--tag=package-2025-04-01-preview` is specified on the command line. + +```yaml $(tag) == 'package-2025-04-01-preview' +input-file: + - preview/2025-04-01-preview/foo.json +``` + +### Tag: package-2025-02-01-preview + +These settings apply only when `--tag=package-2025-02-01-preview` is specified on the command line. + +```yaml $(tag) == 'package-2025-02-01-preview' +input-file: + - preview/2025-02-01-preview/foo.json +``` \ No newline at end of file diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/stable/2025-01-01/foo.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/stable/2025-01-01/foo.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/stable/2025-01-01/foo.json @@ -0,0 +1 @@ +{} diff --git a/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/stable/2025-03-01/foo.json b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/stable/2025-03-01/foo.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/eng/tools/openapi-diff-runner/test/fixtures/specification/foo/data-plane/Foo/stable/2025-03-01/foo.json @@ -0,0 +1 @@ +{}