From e9e836bee99190e547bff2d26194219c2d761182 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 7 Oct 2025 17:37:39 +0000 Subject: [PATCH 1/5] Add failing test --- src/test/avocado-test.ts | 6 +++++ .../Microsoft.Test/Test/readme.md | 25 +++++++++++++++++++ .../Test/stable/2025-07-01/spec.json | 11 ++++++++ .../Microsoft.Test/Test/stable/7.0/spec.json | 11 ++++++++ 4 files changed, 53 insertions(+) create mode 100644 src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/readme.md create mode 100644 src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/2025-07-01/spec.json create mode 100644 src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/7.0/spec.json diff --git a/src/test/avocado-test.ts b/src/test/avocado-test.ts index 512af9e..8dbbebb 100644 --- a/src/test/avocado-test.ts +++ b/src/test/avocado-test.ts @@ -366,6 +366,12 @@ describe('avocado', () => { ) }) + it('avocado check semver api version sorts oldest', async () => { + const r = await avocado.avocado({ cwd: 'src/test/latest_api_version', env: {} }).toArray() + const errorCodes = r.map((e) => e.code) + assert.deepStrictEqual(errorCodes, []) + }) + it('avocado check illegal file, like cadl', async () => { const r = await avocado.avocado({ cwd: 'src/test/contain_cadl_folder', env: {} }).toArray() assert.deepStrictEqual(r.length > 0, true) diff --git a/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/readme.md b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/readme.md new file mode 100644 index 0000000..3674c32 --- /dev/null +++ b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/readme.md @@ -0,0 +1,25 @@ +# latest-api-version + +> see https://aka.ms/autorest + +## Configuration + +### Basic Information + +```yaml +tag: package-2025-07-01 +``` + +### Tag: package-2025-07-01 + +```yaml $(tag) == 'package-2025-07-01' +input-file: + - stable/2025-07-01/spec.json +``` + +### Tag: package-7.0 + +```yaml $(tag) == 'package-7.0' +input-file: + - stable/7.0/spec.json +``` diff --git a/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/2025-07-01/spec.json b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/2025-07-01/spec.json new file mode 100644 index 0000000..c3ef1d6 --- /dev/null +++ b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/2025-07-01/spec.json @@ -0,0 +1,11 @@ +{ + "swagger": "2.0", + "info": { + "version": "2025-07-01" + }, + "paths": { + "/path1": {}, + "/path2": {}, + "/path3": {} + } +} diff --git a/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/7.0/spec.json b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/7.0/spec.json new file mode 100644 index 0000000..9f29f87 --- /dev/null +++ b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/7.0/spec.json @@ -0,0 +1,11 @@ +{ + "swagger": "2.0", + "info": { + "version": "7.0" + }, + "paths": { + "/path1": {}, + "/path2": {}, + "/path3": {} + } +} From 73f84db4f142596ebdaa7346f45ebba43873d066 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 7 Oct 2025 17:44:11 +0000 Subject: [PATCH 2/5] rename test, add comment --- src/test/avocado-test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/avocado-test.ts b/src/test/avocado-test.ts index 8dbbebb..a327db1 100644 --- a/src/test/avocado-test.ts +++ b/src/test/avocado-test.ts @@ -366,7 +366,12 @@ describe('avocado', () => { ) }) - it('avocado check semver api version sorts oldest', async () => { + it('avocado check date-based version is latest', async () => { + // Spec contains two versions "7.0" and "2025-07-01", and default version "2025-07-01". If the versions are only + // sorted alphabetically, "7.0" sorts later than "2025-07-01", which results in error + // "NOT_LATEST_API_VERSION_IN_DEFAULT_TAG". Thus, our runtime code must special-case this, and ensure date-based + // versions always sort "later" than non-data-based-versions. + const r = await avocado.avocado({ cwd: 'src/test/latest_api_version', env: {} }).toArray() const errorCodes = r.map((e) => e.code) assert.deepStrictEqual(errorCodes, []) From 83c7afed03693aeea2dda95e869090c3ae9ec1f6 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 7 Oct 2025 18:19:28 +0000 Subject: [PATCH 3/5] Sort date-based versions first --- src/index.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 9b8139d..ab534fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -469,7 +469,7 @@ export const validateRPMustContainAllLatestApiVersionSwagger = (dir: string): it export const mergePathTable = (pathTable: PathTable, newPathTable: PathTable): PathTable => { for (const [key, value] of newPathTable) { if (pathTable.has(key)) { - if (pathTable.get(key)!.apiVersion < value.apiVersion) { + if (compareApiVersion(pathTable.get(key)!.apiVersion, value.apiVersion) < 0) { pathTable.set(key, value) } } else { @@ -479,6 +479,32 @@ export const mergePathTable = (pathTable: PathTable, newPathTable: PathTable): P return pathTable } +// apiVersion may be date-based ("2025-07-01") or non-date-based ("7.0", "v7.0", etc). All recent versions should be +// date-based, so sort all date-based versions later than all non-date-based versions. +const compareApiVersion = (a: string, b: string): number => { + const dateA = containsDate(a) + const dateB = containsDate(b) + if (dateA && !dateB) { + return 1 + } else if (!dateA && dateB) { + return -1 + } else { + // Both "a" and "b" are either date-based or non-date-based, so perform an invariant string comparison + return invariantCompare(a, b) + } +} + +// Returns true if a string contains a substring that looks like a date "YYYY-MM-DD" +const containsDate = (s: string): boolean => { + const dateRegex = /\d{4}-\d{2}-\d{2}/ + return dateRegex.test(s) +} + +const invariantCompare = (a: string, b: string): number => { + // JS doesn't provide a built-in invariantCompare(), and recommends this + return a.localeCompare(b, /* locales */ 'und', { sensitivity: 'variant' }) +} + export const diffPathTable = (defaultPathTable: PathTable, latestPathTable: PathTable): any[] => { const result: any[] = [] for (const [key, value] of latestPathTable) { From 4a373f3f32d0a0df65ae7aefd51184d255342cca Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 7 Oct 2025 18:21:50 +0000 Subject: [PATCH 4/5] comment --- src/test/avocado-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/avocado-test.ts b/src/test/avocado-test.ts index a327db1..2724d4b 100644 --- a/src/test/avocado-test.ts +++ b/src/test/avocado-test.ts @@ -370,7 +370,7 @@ describe('avocado', () => { // Spec contains two versions "7.0" and "2025-07-01", and default version "2025-07-01". If the versions are only // sorted alphabetically, "7.0" sorts later than "2025-07-01", which results in error // "NOT_LATEST_API_VERSION_IN_DEFAULT_TAG". Thus, our runtime code must special-case this, and ensure date-based - // versions always sort "later" than non-data-based-versions. + // versions always sort "later" than non-date-based-versions. const r = await avocado.avocado({ cwd: 'src/test/latest_api_version', env: {} }).toArray() const errorCodes = r.map((e) => e.code) From b19e0906fd451ed9126049060eaa8c915c1e016a Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 7 Oct 2025 18:37:20 +0000 Subject: [PATCH 5/5] extract code to api-version.ts --- src/api-version.ts | 28 ++++++++++++++++++++++++++++ src/index.ts | 27 +-------------------------- src/test/api-version-test.ts | 25 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 src/api-version.ts create mode 100644 src/test/api-version-test.ts diff --git a/src/api-version.ts b/src/api-version.ts new file mode 100644 index 0000000..404f9be --- /dev/null +++ b/src/api-version.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +// apiVersion may be date-based ("2025-07-01") or non-date-based ("7.0", "v7.0", etc). All recent versions should be +// date-based, so sort all date-based versions later than all non-date-based versions. +export const compareApiVersion = (a: string, b: string): number => { + const dateA = containsDate(a) + const dateB = containsDate(b) + if (dateA && !dateB) { + return 1 + } else if (!dateA && dateB) { + return -1 + } else { + // Both "a" and "b" are either date-based or non-date-based, so perform an invariant string comparison + return invariantCompare(a, b) + } +} + +// Returns true if a string contains a substring that looks like a date "YYYY-MM-DD" +const containsDate = (s: string): boolean => { + const dateRegex = /\d{4}-\d{2}-\d{2}/ + return dateRegex.test(s) +} + +const invariantCompare = (a: string, b: string): number => { + // JS doesn't provide a built-in invariantCompare(), and recommends this + return a.localeCompare(b, /* locales */ 'und', { sensitivity: 'variant' }) +} diff --git a/src/index.ts b/src/index.ts index ab534fe..e187b8b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import * as commonmark from 'commonmark' import * as fs from 'fs' import { JSONPath } from 'jsonpath-plus' import * as path from 'path' +import { compareApiVersion } from './api-version' import * as childProcess from './child-process' import * as cli from './cli' import * as devOps from './dev-ops' @@ -479,32 +480,6 @@ export const mergePathTable = (pathTable: PathTable, newPathTable: PathTable): P return pathTable } -// apiVersion may be date-based ("2025-07-01") or non-date-based ("7.0", "v7.0", etc). All recent versions should be -// date-based, so sort all date-based versions later than all non-date-based versions. -const compareApiVersion = (a: string, b: string): number => { - const dateA = containsDate(a) - const dateB = containsDate(b) - if (dateA && !dateB) { - return 1 - } else if (!dateA && dateB) { - return -1 - } else { - // Both "a" and "b" are either date-based or non-date-based, so perform an invariant string comparison - return invariantCompare(a, b) - } -} - -// Returns true if a string contains a substring that looks like a date "YYYY-MM-DD" -const containsDate = (s: string): boolean => { - const dateRegex = /\d{4}-\d{2}-\d{2}/ - return dateRegex.test(s) -} - -const invariantCompare = (a: string, b: string): number => { - // JS doesn't provide a built-in invariantCompare(), and recommends this - return a.localeCompare(b, /* locales */ 'und', { sensitivity: 'variant' }) -} - export const diffPathTable = (defaultPathTable: PathTable, latestPathTable: PathTable): any[] => { const result: any[] = [] for (const [key, value] of latestPathTable) { diff --git a/src/test/api-version-test.ts b/src/test/api-version-test.ts new file mode 100644 index 0000000..c80768b --- /dev/null +++ b/src/test/api-version-test.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +import * as assert from 'assert' +import { compareApiVersion } from '../api-version' + +describe('api-version', () => { + it.each([ + // date-based versions + ['2025-01-01', '2025-01-01', 0], + ['2025-01-01', '2025-01-02', -1], + ['2025-01-03', '2025-01-02', 1], + // non-date-based versions + ['7.0', '7.0', 0], + ['7.0', '7.1', -1], + ['7.2', '7.1', 1], + // mixed versions, date-based should always sort later + ['7.0', '2025-01-01', -1], + ['2025-01-01', '7.0', 1], + ])('compares API version strings(%s, %s, %d)', (a: string, b: string, result: number) => { + // Compare both ways, ensure results are inverted + assert.equal(compareApiVersion(a, b), result) + assert.equal(compareApiVersion(b, a), result * -1) + }) +})