@typespec/spec-api 0.1.0-alpha.9 → 0.1.0-dev.1
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.
- package/CHANGELOG.md +39 -0
- package/dist/expectation.d.ts +8 -5
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +12 -8
- package/dist/expectation.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/match-engine.d.ts +58 -0
- package/dist/match-engine.d.ts.map +1 -0
- package/dist/match-engine.js +156 -0
- package/dist/match-engine.js.map +1 -0
- package/dist/matchers/datetime.d.ts +8 -0
- package/dist/matchers/datetime.d.ts.map +1 -0
- package/dist/matchers/datetime.js +47 -0
- package/dist/matchers/datetime.js.map +1 -0
- package/dist/matchers/index.d.ts +39 -0
- package/dist/matchers/index.d.ts.map +1 -0
- package/dist/matchers/index.js +36 -0
- package/dist/matchers/index.js.map +1 -0
- package/dist/matchers/local-url.d.ts +3 -0
- package/dist/matchers/local-url.d.ts.map +1 -0
- package/dist/matchers/local-url.js +22 -0
- package/dist/matchers/local-url.js.map +1 -0
- package/dist/request-validations.d.ts +2 -2
- package/dist/request-validations.d.ts.map +1 -1
- package/dist/request-validations.js +54 -19
- package/dist/request-validations.js.map +1 -1
- package/dist/response-utils.d.ts +48 -11
- package/dist/response-utils.d.ts.map +1 -1
- package/dist/response-utils.js +70 -30
- package/dist/response-utils.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +14 -14
- package/src/expectation.ts +13 -9
- package/src/index.ts +10 -0
- package/src/match-engine.ts +221 -0
- package/src/matchers/datetime.ts +66 -0
- package/src/matchers/index.ts +48 -0
- package/src/matchers/local-url.ts +24 -0
- package/src/request-validations.ts +78 -27
- package/src/response-utils.ts +106 -34
- package/src/types.ts +2 -0
- package/temp/.tsbuildinfo +1 -1
- package/test/dyn.test.ts +1 -1
- package/test/match-engine.test.ts +303 -0
- package/test/matchers/datetime.test.ts +232 -0
- package/test/matchers/local-url.test.ts +82 -0
- package/test/matchers/matcher-test-utils.ts +17 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { match } from "../../src/matchers/index.js";
|
|
3
|
+
import { expectFail, expectPass } from "./matcher-test-utils.js";
|
|
4
|
+
|
|
5
|
+
describe("match.dateTime.rfc3339()", () => {
|
|
6
|
+
it("should throw for invalid datetime", () => {
|
|
7
|
+
expect(() => match.dateTime.rfc3339("not-a-date")).toThrow("invalid datetime value");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should throw for empty string", () => {
|
|
11
|
+
expect(() => match.dateTime.rfc3339("")).toThrow("invalid datetime value");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("check()", () => {
|
|
15
|
+
const matcher = match.dateTime.rfc3339("2022-08-26T18:38:00.000Z");
|
|
16
|
+
|
|
17
|
+
it("should match exact same string", () => {
|
|
18
|
+
expectPass(matcher.check("2022-08-26T18:38:00.000Z"));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should match without fractional seconds", () => {
|
|
22
|
+
expectPass(matcher.check("2022-08-26T18:38:00Z"));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should match with extra precision", () => {
|
|
26
|
+
expectPass(matcher.check("2022-08-26T18:38:00.0000000Z"));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should match with 1 fractional digit", () => {
|
|
30
|
+
expectPass(matcher.check("2022-08-26T18:38:00.0Z"));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should match with 2 fractional digits", () => {
|
|
34
|
+
expectPass(matcher.check("2022-08-26T18:38:00.00Z"));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should match with +00:00 offset instead of Z", () => {
|
|
38
|
+
expectPass(matcher.check("2022-08-26T18:38:00.000+00:00"));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should match equivalent time in a different timezone offset", () => {
|
|
42
|
+
expectPass(matcher.check("2022-08-26T14:38:00.000-04:00"));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should reject RFC 7231 format even if same point in time", () => {
|
|
46
|
+
expectFail(matcher.check("Fri, 26 Aug 2022 18:38:00 GMT"), "rfc3339 format");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should not match different time", () => {
|
|
50
|
+
expectFail(matcher.check("2022-08-26T18:39:00.000Z"), "timestamps differ");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should not match off by one second", () => {
|
|
54
|
+
expectFail(matcher.check("2022-08-26T18:38:01.000Z"), "timestamps differ");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should not match different date same time", () => {
|
|
58
|
+
expectFail(matcher.check("2022-08-27T18:38:00.000Z"), "timestamps differ");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should not match non-string values", () => {
|
|
62
|
+
expectFail(matcher.check(12345), "expected a string but got number");
|
|
63
|
+
expectFail(matcher.check(null), "expected a string but got object");
|
|
64
|
+
expectFail(matcher.check(undefined), "expected a string but got undefined");
|
|
65
|
+
expectFail(matcher.check(true), "expected a string but got boolean");
|
|
66
|
+
expectFail(matcher.check({}), "expected a string but got object");
|
|
67
|
+
expectFail(matcher.check([]), "expected a string but got object");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should not match empty string", () => {
|
|
71
|
+
expectFail(matcher.check(""), "rfc3339 format");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should not match invalid datetime strings", () => {
|
|
75
|
+
expectFail(matcher.check("not-a-date"), "rfc3339 format");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("with non-zero milliseconds", () => {
|
|
80
|
+
const matcher = match.dateTime.rfc3339("2022-08-26T18:38:00.123Z");
|
|
81
|
+
|
|
82
|
+
it("should match exact milliseconds", () => {
|
|
83
|
+
expectPass(matcher.check("2022-08-26T18:38:00.123Z"));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should match with trailing zeros", () => {
|
|
87
|
+
expectPass(matcher.check("2022-08-26T18:38:00.1230000Z"));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should not match truncated milliseconds", () => {
|
|
91
|
+
expectFail(matcher.check("2022-08-26T18:38:00Z"), "timestamps differ");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should not match different milliseconds", () => {
|
|
95
|
+
expectFail(matcher.check("2022-08-26T18:38:00.124Z"), "timestamps differ");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("with midnight edge case", () => {
|
|
100
|
+
const matcher = match.dateTime.rfc3339("2022-08-26T00:00:00.000Z");
|
|
101
|
+
|
|
102
|
+
it("should match midnight", () => {
|
|
103
|
+
expectPass(matcher.check("2022-08-26T00:00:00Z"));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should match midnight with offset expressing previous day", () => {
|
|
107
|
+
expectPass(matcher.check("2022-08-25T20:00:00-04:00"));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("serialize()", () => {
|
|
112
|
+
it("should return the original value", () => {
|
|
113
|
+
expect(match.dateTime.rfc3339("2022-08-26T18:38:00.000Z").serialize()).toBe(
|
|
114
|
+
"2022-08-26T18:38:00.000Z",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should serialize correctly in JSON.stringify", () => {
|
|
119
|
+
const obj = { value: match.dateTime.rfc3339("2022-08-26T18:38:00.000Z") };
|
|
120
|
+
expect(JSON.stringify(obj)).toBe('{"value":"2022-08-26T18:38:00.000Z"}');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe("toString()", () => {
|
|
124
|
+
it("should include rfc3339 in toString()", () => {
|
|
125
|
+
expect(match.dateTime.rfc3339("2022-08-26T18:38:00.000Z").toString()).toBe(
|
|
126
|
+
"match.dateTime.rfc3339(2022-08-26T18:38:00.000Z)",
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("match.dateTime.rfc7231()", () => {
|
|
133
|
+
it("should throw for invalid datetime", () => {
|
|
134
|
+
expect(() => match.dateTime.rfc7231("not-a-date")).toThrow("invalid datetime value");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("check()", () => {
|
|
138
|
+
const matcher = match.dateTime.rfc7231("Fri, 26 Aug 2022 14:38:00 GMT");
|
|
139
|
+
|
|
140
|
+
it("should match exact same string", () => {
|
|
141
|
+
expectPass(matcher.check("Fri, 26 Aug 2022 14:38:00 GMT"));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should reject RFC 3339 format even if same point in time", () => {
|
|
145
|
+
expectFail(matcher.check("2022-08-26T14:38:00.000Z"), "rfc7231 format");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should not match different time", () => {
|
|
149
|
+
expectFail(matcher.check("Fri, 26 Aug 2022 14:39:00 GMT"), "timestamps differ");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should not match non-string values", () => {
|
|
153
|
+
expectFail(matcher.check(12345), "expected a string but got number");
|
|
154
|
+
expectFail(matcher.check(null), "expected a string but got object");
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("serialize()", () => {
|
|
159
|
+
it("should preserve RFC 7231 format", () => {
|
|
160
|
+
expect(match.dateTime.rfc7231("Fri, 26 Aug 2022 14:38:00 GMT").serialize()).toBe(
|
|
161
|
+
"Fri, 26 Aug 2022 14:38:00 GMT",
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("match.dateTime.utcRfc3339()", () => {
|
|
168
|
+
it("should throw for invalid datetime", () => {
|
|
169
|
+
expect(() => match.dateTime.utcRfc3339("not-a-date")).toThrow("invalid datetime value");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should throw for empty string", () => {
|
|
173
|
+
expect(() => match.dateTime.utcRfc3339("")).toThrow("invalid datetime value");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("check()", () => {
|
|
177
|
+
const matcher = match.dateTime.utcRfc3339("2022-08-26T18:38:00.000Z");
|
|
178
|
+
|
|
179
|
+
it("should match exact same string", () => {
|
|
180
|
+
expectPass(matcher.check("2022-08-26T18:38:00.000Z"));
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should match without fractional seconds", () => {
|
|
184
|
+
expectPass(matcher.check("2022-08-26T18:38:00Z"));
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should match with extra precision", () => {
|
|
188
|
+
expectPass(matcher.check("2022-08-26T18:38:00.0000000Z"));
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should reject +00:00 offset even though equivalent to Z", () => {
|
|
192
|
+
expectFail(matcher.check("2022-08-26T18:38:00.000+00:00"), "utcRfc3339 format");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should reject timezone offset", () => {
|
|
196
|
+
expectFail(matcher.check("2022-08-26T14:38:00.000-04:00"), "utcRfc3339 format");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should reject positive timezone offset", () => {
|
|
200
|
+
expectFail(matcher.check("2022-08-26T20:38:00.000+02:00"), "utcRfc3339 format");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should reject RFC 7231 format", () => {
|
|
204
|
+
expectFail(matcher.check("Fri, 26 Aug 2022 18:38:00 GMT"), "utcRfc3339 format");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should not match different time", () => {
|
|
208
|
+
expectFail(matcher.check("2022-08-26T18:39:00.000Z"), "timestamps differ");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should not match non-string values", () => {
|
|
212
|
+
expectFail(matcher.check(12345), "expected a string but got number");
|
|
213
|
+
expectFail(matcher.check(null), "expected a string but got object");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("serialize()", () => {
|
|
218
|
+
it("should return the original value", () => {
|
|
219
|
+
expect(match.dateTime.utcRfc3339("2022-08-26T18:38:00.000Z").serialize()).toBe(
|
|
220
|
+
"2022-08-26T18:38:00.000Z",
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe("toString()", () => {
|
|
226
|
+
it("should include utcRfc3339 in toString()", () => {
|
|
227
|
+
expect(match.dateTime.utcRfc3339("2022-08-26T18:38:00.000Z").toString()).toBe(
|
|
228
|
+
"match.dateTime.utcRfc3339(2022-08-26T18:38:00.000Z)",
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isMatcher, type MatcherConfig } from "../../src/match-engine.js";
|
|
3
|
+
import { match } from "../../src/matchers/index.js";
|
|
4
|
+
import { expectFail, expectPass } from "./matcher-test-utils.js";
|
|
5
|
+
|
|
6
|
+
const config: MatcherConfig = { baseUrl: "http://localhost:3000" };
|
|
7
|
+
|
|
8
|
+
describe("match.localUrl()", () => {
|
|
9
|
+
it("should be identified by isMatcher", () => {
|
|
10
|
+
expect(isMatcher(match.localUrl("/some/path"))).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("check()", () => {
|
|
14
|
+
const matcher = match.localUrl("/payload/pageable/next-page");
|
|
15
|
+
|
|
16
|
+
it("should match exact full URL", () => {
|
|
17
|
+
expectPass(matcher.check("http://localhost:3000/payload/pageable/next-page", config));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should not match a different base URL", () => {
|
|
21
|
+
expectFail(
|
|
22
|
+
matcher.check("http://localhost:4000/payload/pageable/next-page", config),
|
|
23
|
+
"match.localUrl",
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should not match a different path", () => {
|
|
28
|
+
expectFail(
|
|
29
|
+
matcher.check("http://localhost:3000/payload/pageable/other-page", config),
|
|
30
|
+
"match.localUrl",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should not match non-string values", () => {
|
|
35
|
+
expectFail(matcher.check(42, config), "expected a string but got number");
|
|
36
|
+
expectFail(matcher.check(null, config), "expected a string but got object");
|
|
37
|
+
expectFail(matcher.check(undefined, config), "expected a string but got undefined");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("serialize()", () => {
|
|
42
|
+
it("should return the full URL with config", () => {
|
|
43
|
+
expect(match.localUrl("/some/path").serialize(config)).toBe(
|
|
44
|
+
"http://localhost:3000/some/path",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should serialize correctly in JSON.stringify", () => {
|
|
49
|
+
const obj = { nextLink: match.localUrl("/some/path") };
|
|
50
|
+
// toJSON() uses empty config, so just the path
|
|
51
|
+
expect(JSON.stringify(obj)).toBe('{"nextLink":"/some/path"}');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("resolution with different base URLs", () => {
|
|
56
|
+
const matcher = match.localUrl("/api/items");
|
|
57
|
+
|
|
58
|
+
it("should resolve with localhost", () => {
|
|
59
|
+
expectPass(
|
|
60
|
+
matcher.check("http://localhost:3000/api/items", { baseUrl: "http://localhost:3000" }),
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should resolve with https URL", () => {
|
|
65
|
+
expectPass(
|
|
66
|
+
matcher.check("https://example.com/api/items", { baseUrl: "https://example.com" }),
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should resolve with URL including port", () => {
|
|
71
|
+
expectPass(
|
|
72
|
+
matcher.check("http://127.0.0.1:8080/api/items", { baseUrl: "http://127.0.0.1:8080" }),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("toString()", () => {
|
|
78
|
+
it("should return a descriptive string", () => {
|
|
79
|
+
expect(match.localUrl("/some/path").toString()).toBe('match.localUrl("/some/path")');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { expect } from "vitest";
|
|
2
|
+
import { MatchResult } from "../../src/match-engine.js";
|
|
3
|
+
|
|
4
|
+
export function expectPass(result: MatchResult) {
|
|
5
|
+
expect(result).toEqual({ pass: true });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function expectFail(result: MatchResult, messagePattern?: string | RegExp) {
|
|
9
|
+
expect(result.pass).toBe(false);
|
|
10
|
+
if (!result.pass && messagePattern) {
|
|
11
|
+
if (typeof messagePattern === "string") {
|
|
12
|
+
expect(result.message).toContain(messagePattern);
|
|
13
|
+
} else {
|
|
14
|
+
expect(result.message).toMatch(messagePattern);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|