|
| 1 | +import { describe, expect, it } from "vitest"; |
| 2 | +import { |
| 3 | + getServerPathPrefixSegmentCount, |
| 4 | + isMockApiUriConsistentWithRoute, |
| 5 | + normalizeMockApiUri, |
| 6 | +} from "./route-utils.js"; |
| 7 | + |
| 8 | +describe("normalizeMockApiUri", () => { |
| 9 | + it("drops the query string", () => { |
| 10 | + expect(normalizeMockApiUri("/foo/bar?baz=1")).toBe("/foo/bar"); |
| 11 | + }); |
| 12 | + |
| 13 | + it("removes backslash escapes", () => { |
| 14 | + expect(normalizeMockApiUri("/versioning/removed/api-version\\:v1/v3")).toBe( |
| 15 | + "/versioning/removed/api-version:v1/v3", |
| 16 | + ); |
| 17 | + }); |
| 18 | +}); |
| 19 | + |
| 20 | +describe("getServerPathPrefixSegmentCount", () => { |
| 21 | + it("returns 0 for an undefined server", () => { |
| 22 | + expect(getServerPathPrefixSegmentCount(undefined)).toBe(0); |
| 23 | + }); |
| 24 | + |
| 25 | + it("returns 0 for the default localhost server", () => { |
| 26 | + expect(getServerPathPrefixSegmentCount("http://localhost:3000")).toBe(0); |
| 27 | + }); |
| 28 | + |
| 29 | + it("returns 0 for a bare host server (e.g. ARM)", () => { |
| 30 | + expect(getServerPathPrefixSegmentCount("https://management.azure.com")).toBe(0); |
| 31 | + }); |
| 32 | + |
| 33 | + it("counts the path segments after an {endpoint} template", () => { |
| 34 | + expect( |
| 35 | + getServerPathPrefixSegmentCount( |
| 36 | + "{endpoint}/resiliency/service-driven/client:v2/service:{serviceDeploymentVersion}/api-version:{apiVersion}", |
| 37 | + ), |
| 38 | + ).toBe(5); |
| 39 | + }); |
| 40 | + |
| 41 | + it("counts the path segments after localhost:3000", () => { |
| 42 | + expect(getServerPathPrefixSegmentCount("http://localhost:3000/my/prefix")).toBe(2); |
| 43 | + }); |
| 44 | +}); |
| 45 | + |
| 46 | +describe("isMockApiUriConsistentWithRoute", () => { |
| 47 | + it("matches identical routes", () => { |
| 48 | + expect( |
| 49 | + isMockApiUriConsistentWithRoute("/parameters/basic/simple", "/parameters/basic/simple"), |
| 50 | + ).toBe(true); |
| 51 | + }); |
| 52 | + |
| 53 | + it("detects a mismatch in a literal segment", () => { |
| 54 | + // A literal route segment must match exactly: `dollarSign` (camelCase) must not be considered |
| 55 | + // consistent with a `dollar-sign` (kebab-case) uri. |
| 56 | + expect( |
| 57 | + isMockApiUriConsistentWithRoute( |
| 58 | + "/parameters/query/special-char/dollarSign", |
| 59 | + "/parameters/query/special-char/dollar-sign", |
| 60 | + ), |
| 61 | + ).toBe(false); |
| 62 | + }); |
| 63 | + |
| 64 | + it("detects swapped routes", () => { |
| 65 | + expect(isMockApiUriConsistentWithRoute("/routes/fixed", "/routes/in-interface/fixed")).toBe( |
| 66 | + false, |
| 67 | + ); |
| 68 | + }); |
| 69 | + |
| 70 | + it("treats whole-segment template expressions as wildcards", () => { |
| 71 | + expect( |
| 72 | + isMockApiUriConsistentWithRoute( |
| 73 | + "/routes/path/template-only/{param}", |
| 74 | + "/routes/path/template-only/a", |
| 75 | + ), |
| 76 | + ).toBe(true); |
| 77 | + }); |
| 78 | + |
| 79 | + it("matches template expressions embedded in a segment", () => { |
| 80 | + expect( |
| 81 | + isMockApiUriConsistentWithRoute( |
| 82 | + "/routes/path/simple/standard/primitive{param}", |
| 83 | + "/routes/path/simple/standard/primitivea", |
| 84 | + ), |
| 85 | + ).toBe(true); |
| 86 | + }); |
| 87 | + |
| 88 | + it("matches path expansion template expressions that expand to extra segments", () => { |
| 89 | + expect( |
| 90 | + isMockApiUriConsistentWithRoute( |
| 91 | + "/routes/path/path/standard/primitive{param}", |
| 92 | + "/routes/path/path/standard/primitive/a", |
| 93 | + ), |
| 94 | + ).toBe(true); |
| 95 | + }); |
| 96 | + |
| 97 | + it("detects a uri that is missing a trailing literal segment present in the route", () => { |
| 98 | + expect(isMockApiUriConsistentWithRoute("/parameters/basic/simple", "/parameters/basic")).toBe( |
| 99 | + false, |
| 100 | + ); |
| 101 | + }); |
| 102 | + |
| 103 | + it("ignores trailing slashes", () => { |
| 104 | + expect( |
| 105 | + isMockApiUriConsistentWithRoute( |
| 106 | + "/azure/special-headers/x-ms-client-request-id/", |
| 107 | + "/azure/special-headers/x-ms-client-request-id", |
| 108 | + ), |
| 109 | + ).toBe(true); |
| 110 | + }); |
| 111 | + |
| 112 | + it("ignores the query string of both the route and the uri", () => { |
| 113 | + expect( |
| 114 | + isMockApiUriConsistentWithRoute( |
| 115 | + "/routes/query/query-continuation/standard/primitive?fixed=true", |
| 116 | + "/routes/query/query-continuation/standard/primitive?fixed=true¶m=a", |
| 117 | + ), |
| 118 | + ).toBe(true); |
| 119 | + }); |
| 120 | + |
| 121 | + it("matches routes containing escaped characters in the uri", () => { |
| 122 | + expect( |
| 123 | + isMockApiUriConsistentWithRoute( |
| 124 | + "/versioning/removed/api-version:{version}/v3", |
| 125 | + "/versioning/removed/api-version\\:v1/v3", |
| 126 | + ), |
| 127 | + ).toBe(true); |
| 128 | + }); |
| 129 | + |
| 130 | + describe("with a server path prefix", () => { |
| 131 | + // Resiliency service-driven: the api-version/client/service version segments come from the |
| 132 | + // `@server` url and may legitimately differ from the mock uri (e.g. client:v1 vs client:v2). |
| 133 | + const serverPrefix = getServerPathPrefixSegmentCount( |
| 134 | + "{endpoint}/resiliency/service-driven/client:v2/service:{serviceDeploymentVersion}/api-version:{apiVersion}", |
| 135 | + ); |
| 136 | + |
| 137 | + it("skips the server prefix segments before comparing", () => { |
| 138 | + expect( |
| 139 | + isMockApiUriConsistentWithRoute( |
| 140 | + "/add-optional-param/from-none", |
| 141 | + "/resiliency/service-driven/client\\:v1/service\\:v1/api-version\\:v1/add-optional-param/from-none", |
| 142 | + serverPrefix, |
| 143 | + ), |
| 144 | + ).toBe(true); |
| 145 | + }); |
| 146 | + |
| 147 | + it("still detects a mismatch in the operation route after the server prefix", () => { |
| 148 | + expect( |
| 149 | + isMockApiUriConsistentWithRoute( |
| 150 | + "/add-optional-param/from-none", |
| 151 | + "/resiliency/service-driven/client\\:v2/service\\:v2/api-version\\:v2/add-operation", |
| 152 | + serverPrefix, |
| 153 | + ), |
| 154 | + ).toBe(false); |
| 155 | + }); |
| 156 | + }); |
| 157 | + |
| 158 | + describe("ARM routes", () => { |
| 159 | + it("matches a fully resolved ARM tracked resource route", () => { |
| 160 | + expect( |
| 161 | + isMockApiUriConsistentWithRoute( |
| 162 | + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Azure.ResourceManager.Resources/topLevelTrackedResources/{topLevelTrackedResourceName}", |
| 163 | + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.Resources/topLevelTrackedResources/top", |
| 164 | + ), |
| 165 | + ).toBe(true); |
| 166 | + }); |
| 167 | + |
| 168 | + it("matches an extension resource route whose {resourceUri} spans several scope segments", () => { |
| 169 | + const route = |
| 170 | + "/{resourceUri}/providers/Azure.ResourceManager.Resources/extensionsResources/{extensionsResourceName}"; |
| 171 | + expect( |
| 172 | + isMockApiUriConsistentWithRoute( |
| 173 | + route, |
| 174 | + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.Resources/extensionsResources/extension", |
| 175 | + ), |
| 176 | + ).toBe(true); |
| 177 | + expect( |
| 178 | + isMockApiUriConsistentWithRoute( |
| 179 | + route, |
| 180 | + "/providers/Azure.ResourceManager.Resources/extensionsResources/extension", |
| 181 | + ), |
| 182 | + ).toBe(true); |
| 183 | + }); |
| 184 | + }); |
| 185 | +}); |
0 commit comments