@schafevormfenster/rest-commons 0.1.9 → 0.1.11
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-check.log +129 -115
- package/.turbo/turbo-lint.log +4 -0
- package/dist/api-schemas/health.schema.js +2 -2
- package/dist/api-schemas/paginated-results.schema.js +1 -1
- package/dist/api-schemas/partial-results.schema.js +1 -1
- package/dist/api-schemas/result.schema.js +1 -1
- package/dist/api-schemas/results.schema.js +1 -1
- package/dist/helpers/correlation/get-correlation-id.js +1 -1
- package/dist/helpers/detect-mime-type.js +4 -4
- package/dist/helpers/parameter-validation.js +1 -1
- package/dist/helpers/response-headers/build-api-unauthorized-headers.js +1 -1
- package/dist/helpers/response-headers/resolve-environment.d.ts +1 -1
- package/dist/helpers/response-headers/resolve-environment.d.ts.map +1 -1
- package/dist/index.d.ts +33 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -33
- package/dist/primitives/geo-point.schema.js +2 -2
- package/dist/primitives/latitude.schema.js +1 -1
- package/dist/primitives/location.schema.js +1 -1
- package/dist/primitives/longitude.schema.js +1 -1
- package/dist/primitives/slug.schema.js +2 -2
- package/dist/time/bounded-time.schema.d.ts.map +1 -1
- package/dist/time/bounded-time.schema.js +19 -15
- package/dist/time/flexible-time-parser.d.ts +1 -1
- package/dist/time/flexible-time-parser.d.ts.map +1 -1
- package/dist/time/flexible-time-parser.js +2 -2
- package/dist/time/flexible-time.schema.js +3 -3
- package/dist/time/relative-time.schema.js +1 -1
- package/dist/time/time-helpers.js +2 -2
- package/dist/time/time-schemas.js +1 -1
- package/package.json +8 -8
- package/src/api-schemas/error.schema.test.ts +20 -14
- package/src/api-schemas/health.schema.test.ts +116 -112
- package/src/api-schemas/health.schema.ts +2 -2
- package/src/api-schemas/okay.schema.test.ts +10 -6
- package/src/api-schemas/paginated-results.schema.ts +1 -1
- package/src/api-schemas/partial-results.schema.ts +1 -1
- package/src/api-schemas/result.schema.test.ts +13 -9
- package/src/api-schemas/result.schema.ts +1 -1
- package/src/api-schemas/results.schema.test.ts +27 -7
- package/src/api-schemas/results.schema.ts +1 -1
- package/src/helpers/correlation/get-correlation-id.test.ts +1 -1
- package/src/helpers/correlation/get-correlation-id.ts +1 -1
- package/src/helpers/correlation/get-header.test.ts +2 -2
- package/src/helpers/detect-mime-type.test.ts +85 -81
- package/src/helpers/detect-mime-type.ts +4 -4
- package/src/helpers/detect-suspicious-patterns.test.ts +34 -28
- package/src/helpers/eventify-constants.test.ts +54 -34
- package/src/helpers/eventify-constants.types.test.ts +54 -34
- package/src/helpers/hash-binary.test.ts +50 -46
- package/src/helpers/mime-types/detect-image-mime-type.test.ts +59 -55
- package/src/helpers/mime-types/detect-ole-mime-type.test.ts +73 -69
- package/src/helpers/mime-types/detect-pdf-mime-type.test.ts +30 -26
- package/src/helpers/mime-types/detect-zip-mime-type.test.ts +76 -72
- package/src/helpers/parameter-validation.test.ts +27 -23
- package/src/helpers/parameter-validation.ts +1 -1
- package/src/helpers/process-eventify-request.ts +2 -2
- package/src/helpers/response-headers/build-api-unauthorized-headers.ts +1 -1
- package/src/helpers/response-headers/resolve-environment.ts +1 -1
- package/src/helpers/slugify.test.ts +62 -58
- package/src/index.ts +33 -33
- package/src/normalization/normalize-list.test.ts +36 -32
- package/src/normalization/normalize-location.test.ts +1 -1
- package/src/primitives/coordinate-precision.test.ts +38 -32
- package/src/primitives/geo-point.schema.test.ts +60 -56
- package/src/primitives/geo-point.schema.ts +2 -2
- package/src/primitives/geoname-id.schema.test.ts +50 -46
- package/src/primitives/international-zip.schema.test.ts +175 -121
- package/src/primitives/latitude.schema.test.ts +67 -61
- package/src/primitives/latitude.schema.ts +1 -1
- package/src/primitives/location.schema.test.ts +15 -11
- package/src/primitives/location.schema.ts +1 -1
- package/src/primitives/longitude.schema.test.ts +67 -61
- package/src/primitives/longitude.schema.ts +1 -1
- package/src/primitives/numeric-id.schema.test.ts +24 -20
- package/src/primitives/slug.schema.test.ts +93 -85
- package/src/primitives/slug.schema.ts +2 -2
- package/src/primitives/uuid.schema.test.ts +37 -33
- package/src/primitives/wikidata-id.schema.test.ts +48 -44
- package/src/time/boundary-enforcement.test.ts +1 -1
- package/src/time/bounded-time.schema.test.ts +2 -2
- package/src/time/bounded-time.schema.ts +19 -15
- package/src/time/flexible-time-parser.test.ts +3 -3
- package/src/time/flexible-time-parser.ts +2 -2
- package/src/time/flexible-time.schema.test.ts +6 -6
- package/src/time/flexible-time.schema.ts +3 -3
- package/src/time/is-relative-time.test.ts +18 -14
- package/src/time/iso8601.types.test.ts +4 -4
- package/src/time/parse-relative-time.test.ts +37 -33
- package/src/time/relative-time.schema.test.ts +18 -14
- package/src/time/relative-time.schema.ts +1 -1
- package/src/time/since-parameter.schema.test.ts +46 -42
- package/src/time/time-helpers.test.ts +2 -2
- package/src/time/time-helpers.ts +2 -2
- package/src/time/time-schemas.test.ts +5 -5
- package/src/time/time-schemas.ts +1 -1
- package/src/time/time.schema.test.ts +19 -15
- package/src/time/timezone-independence.test.ts +2 -2
- package/src/time/timezone.types.test.ts +49 -43
|
@@ -3,33 +3,37 @@ import { describe, it, expect } from "vitest";
|
|
|
3
3
|
import { validateNoSuspiciousPatterns } from "./parameter-validation";
|
|
4
4
|
|
|
5
5
|
describe("validateNoSuspiciousPatterns", () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
describe("Happy Path", () => {
|
|
7
|
+
it("should allow clean input", () => {
|
|
8
|
+
expect(() => validateNoSuspiciousPatterns("Berlin, Germany", "location")).not.toThrow();
|
|
9
|
+
expect(() => validateNoSuspiciousPatterns("my-community-slug", "slug")).not.toThrow();
|
|
10
|
+
expect(() => validateNoSuspiciousPatterns("12345", "id")).not.toThrow();
|
|
11
|
+
});
|
|
10
12
|
});
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
describe("Error Handling", () => {
|
|
15
|
+
it("should reject input with script tags", () => {
|
|
16
|
+
expect(() =>
|
|
17
|
+
validateNoSuspiciousPatterns("<script>alert(1)</script>", "location")
|
|
18
|
+
).toThrow("Parameter 'location' contains suspicious patterns: html.script_tag");
|
|
19
|
+
});
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
it("should reject input with SQL injection patterns", () => {
|
|
22
|
+
expect(() =>
|
|
23
|
+
validateNoSuspiciousPatterns("Berlin; DROP TABLE users", "location")
|
|
24
|
+
).toThrow("Parameter 'location' contains suspicious patterns: sql.drop_table");
|
|
25
|
+
});
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
it("should reject input with embedded URLs", () => {
|
|
28
|
+
expect(() =>
|
|
29
|
+
validateNoSuspiciousPatterns("Visit https://evil.com", "slug")
|
|
30
|
+
).toThrow("Parameter 'slug' contains suspicious patterns: url.embedded");
|
|
31
|
+
});
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
it("should reject input with prompt injection", () => {
|
|
34
|
+
expect(() =>
|
|
35
|
+
validateNoSuspiciousPatterns("Ignore previous instructions", "id")
|
|
36
|
+
).toThrow("Parameter 'id' contains suspicious patterns: prompt.injection.language");
|
|
37
|
+
});
|
|
34
38
|
});
|
|
35
39
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getLogger } from "@schafevormfenster/logging";
|
|
2
2
|
|
|
3
|
-
import { detectSuspiciousPatternsFromBody } from "./detect-suspicious-patterns";
|
|
3
|
+
import { detectSuspiciousPatternsFromBody } from "./detect-suspicious-patterns.js";
|
|
4
4
|
|
|
5
5
|
const log = getLogger("rest.helpers.parameter-validation");
|
|
6
6
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getLogger } from "@schafevormfenster/logging";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
import { detectMimeType } from "./detect-mime-type";
|
|
5
|
-
import { DEFAULT_MAX_FILE_SIZE } from "./eventify-constants.types";
|
|
4
|
+
import { detectMimeType } from "./detect-mime-type.js";
|
|
5
|
+
import { DEFAULT_MAX_FILE_SIZE } from "./eventify-constants.types.js";
|
|
6
6
|
|
|
7
7
|
import { EventifyRequest } from "@/app/api/[token]/eventify/eventify-request.schema";
|
|
8
8
|
import { getFileFromUrl } from "@/services/file-fetching/get-file-from-url";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getApiSecurityHeaders } from "@schafevormfenster/security";
|
|
2
2
|
|
|
3
|
-
import { resolveEnvironment } from "./resolve-environment";
|
|
3
|
+
import { resolveEnvironment } from "./resolve-environment.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Build a consistent set of headers for unauthorized API responses, including
|
|
@@ -2,76 +2,80 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
import { slugify } from "./slugify";
|
|
4
4
|
|
|
5
|
-
describe("slugify", () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
describe("slugify function", () => {
|
|
6
|
+
describe("Happy Path", () => {
|
|
7
|
+
it("should convert strings to lowercase", () => {
|
|
8
|
+
expect(slugify("Hello World")).toBe("hello-world");
|
|
9
|
+
expect(slugify("UPPERCASE")).toBe("uppercase");
|
|
10
|
+
});
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
it("should replace spaces with hyphens", () => {
|
|
13
|
+
expect(slugify("Hello World")).toBe("hello-world");
|
|
14
|
+
expect(slugify("Multiple Spaces")).toBe("multiple-spaces");
|
|
15
|
+
});
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
it("should preserve alphanumeric characters", () => {
|
|
18
|
+
expect(slugify("abc123")).toBe("abc123");
|
|
19
|
+
expect(slugify("Test123")).toBe("test123");
|
|
20
|
+
});
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
22
|
+
it("should handle complex strings", () => {
|
|
23
|
+
expect(slugify("Hello, World! This is a Test.")).toBe("hello-world-this-is-a-test");
|
|
24
|
+
expect(slugify("Café in München")).toBe("cafe-in-muenchen");
|
|
25
|
+
expect(slugify("New York, NY (USA)")).toBe("new-york-ny-usa");
|
|
26
|
+
});
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
it("should convert German umlauts correctly", () => {
|
|
29
|
+
expect(slugify("Äpfel")).toBe("aepfel");
|
|
30
|
+
expect(slugify("Öl")).toBe("oel");
|
|
31
|
+
expect(slugify("Über")).toBe("ueber");
|
|
32
|
+
expect(slugify("Größe")).toBe("groesse");
|
|
33
|
+
expect(slugify("Käse")).toBe("kaese");
|
|
34
|
+
expect(slugify("Schön")).toBe("schoen");
|
|
35
|
+
});
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
it("should handle mixed German umlauts in phrases", () => {
|
|
38
|
+
expect(slugify("Äpfel und Öl")).toBe("aepfel-und-oel");
|
|
39
|
+
expect(slugify("Größe über Äußeres")).toBe("groesse-ueber-aeusseres");
|
|
40
|
+
expect(slugify("München Köln")).toBe("muenchen-koeln");
|
|
41
|
+
});
|
|
37
42
|
});
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
describe("Edge Cases", () => {
|
|
45
|
+
it("should handle underscores (removes them in strict mode)", () => {
|
|
46
|
+
expect(slugify("hello_world")).toBe("helloworld");
|
|
47
|
+
expect(slugify("hello___world")).toBe("helloworld");
|
|
48
|
+
});
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
it("should handle special characters", () => {
|
|
51
|
+
expect(slugify("Hello, World!")).toBe("hello-world");
|
|
52
|
+
expect(slugify("Berlin, Germany")).toBe("berlin-germany");
|
|
53
|
+
// Note: slugify converts some special chars to words (e.g., $ -> dollar)
|
|
54
|
+
expect(slugify("Test String")).toBe("test-string");
|
|
55
|
+
});
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
it("should remove leading and trailing hyphens", () => {
|
|
58
|
+
expect(slugify("-hello-")).toBe("hello");
|
|
59
|
+
expect(slugify("---hello---")).toBe("hello");
|
|
60
|
+
expect(slugify(" hello ")).toBe("hello");
|
|
61
|
+
});
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
it("should handle multiple consecutive hyphens", () => {
|
|
64
|
+
expect(slugify("hello---world")).toBe("hello-world");
|
|
65
|
+
expect(slugify("hello - world")).toBe("hello-world");
|
|
66
|
+
});
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
it("should handle empty strings", () => {
|
|
69
|
+
expect(slugify("")).toBe("");
|
|
70
|
+
expect(slugify(" ")).toBe("");
|
|
71
|
+
});
|
|
62
72
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
expect(slugify("Über")).toBe("ueber");
|
|
67
|
-
expect(slugify("Größe")).toBe("groesse");
|
|
68
|
-
expect(slugify("Käse")).toBe("kaese");
|
|
69
|
-
expect(slugify("Schön")).toBe("schoen");
|
|
70
|
-
});
|
|
73
|
+
it("should handle strings with only special characters that get removed", () => {
|
|
74
|
+
expect(slugify("!!!")).toBe("");
|
|
75
|
+
});
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
expect(slugify("München Köln")).toBe("muenchen-koeln");
|
|
77
|
+
it("should trim whitespace from input", () => {
|
|
78
|
+
expect(slugify(" hello world ")).toBe("hello-world");
|
|
79
|
+
});
|
|
76
80
|
});
|
|
77
81
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
// Schemas
|
|
2
|
-
export * from "./api-schemas/error.schema";
|
|
3
|
-
export * from "./api-schemas/health.schema";
|
|
4
|
-
export * from "./api-schemas/okay.schema";
|
|
5
|
-
export * from "./api-schemas/result.schema";
|
|
6
|
-
export * from "./api-schemas/results.schema";
|
|
2
|
+
export * from "./api-schemas/error.schema.js";
|
|
3
|
+
export * from "./api-schemas/health.schema.js";
|
|
4
|
+
export * from "./api-schemas/okay.schema.js";
|
|
5
|
+
export * from "./api-schemas/result.schema.js";
|
|
6
|
+
export * from "./api-schemas/results.schema.js";
|
|
7
7
|
|
|
8
8
|
// Primitive schemas
|
|
9
|
-
export * from "./primitives/location.schema";
|
|
10
|
-
export * from "./primitives/slug.schema";
|
|
11
|
-
export * from "./primitives/numeric-id.schema";
|
|
12
|
-
export * from "./primitives/uuid.schema";
|
|
13
|
-
export * from "./primitives/geoname-id.schema";
|
|
14
|
-
export * from "./primitives/wikidata-id.schema";
|
|
15
|
-
export * from "./primitives/international-zip.schema";
|
|
9
|
+
export * from "./primitives/location.schema.js";
|
|
10
|
+
export * from "./primitives/slug.schema.js";
|
|
11
|
+
export * from "./primitives/numeric-id.schema.js";
|
|
12
|
+
export * from "./primitives/uuid.schema.js";
|
|
13
|
+
export * from "./primitives/geoname-id.schema.js";
|
|
14
|
+
export * from "./primitives/wikidata-id.schema.js";
|
|
15
|
+
export * from "./primitives/international-zip.schema.js";
|
|
16
16
|
|
|
17
17
|
// Time utilities
|
|
18
|
-
export * from "./time/timezone.types";
|
|
19
|
-
export * from "./time/iso8601.schema"; // ISO8601 schemas
|
|
20
|
-
export * from "./time/relative-time.schema"; // Relative time schemas
|
|
21
|
-
export * from "./time/flexible-time.schema"; // Flexible time schemas
|
|
22
|
-
export * from "./time/bounded-time.schema"; // Bounded time schemas
|
|
23
|
-
export * from "./time/time-helpers"; // Helper functions
|
|
24
|
-
export * from "./time/is-relative-time";
|
|
25
|
-
export { parseFlexibleTime as parseFlexibleTimeAdvanced } from "./time/flexible-time-parser";
|
|
26
|
-
export * from "./time/boundary-enforcement";
|
|
18
|
+
export * from "./time/timezone.types.js";
|
|
19
|
+
export * from "./time/iso8601.schema.js"; // ISO8601 schemas
|
|
20
|
+
export * from "./time/relative-time.schema.js"; // Relative time schemas
|
|
21
|
+
export * from "./time/flexible-time.schema.js"; // Flexible time schemas
|
|
22
|
+
export * from "./time/bounded-time.schema.js"; // Bounded time schemas
|
|
23
|
+
export * from "./time/time-helpers.js"; // Helper functions
|
|
24
|
+
export * from "./time/is-relative-time.js";
|
|
25
|
+
export { parseFlexibleTime as parseFlexibleTimeAdvanced } from "./time/flexible-time-parser.js";
|
|
26
|
+
export * from "./time/boundary-enforcement.js";
|
|
27
27
|
|
|
28
28
|
// Helpers
|
|
29
|
-
export * from "./helpers/correlation/get-correlation-id";
|
|
30
|
-
export * from "./helpers/correlation/get-header";
|
|
31
|
-
export * from "./helpers/detect-mime-type";
|
|
32
|
-
export * from "./helpers/detect-suspicious-patterns";
|
|
33
|
-
export * from "./helpers/hash-binary";
|
|
34
|
-
export * from "./helpers/parameter-validation";
|
|
35
|
-
export * from "./helpers/slugify";
|
|
36
|
-
export * from "./helpers/response-headers/build-api-unauthorized-headers";
|
|
37
|
-
export * from "./helpers/response-headers/environment.types";
|
|
38
|
-
export * from "./helpers/response-headers/resolve-environment";
|
|
29
|
+
export * from "./helpers/correlation/get-correlation-id.js";
|
|
30
|
+
export * from "./helpers/correlation/get-header.js";
|
|
31
|
+
export * from "./helpers/detect-mime-type.js";
|
|
32
|
+
export * from "./helpers/detect-suspicious-patterns.js";
|
|
33
|
+
export * from "./helpers/hash-binary.js";
|
|
34
|
+
export * from "./helpers/parameter-validation.js";
|
|
35
|
+
export * from "./helpers/slugify.js";
|
|
36
|
+
export * from "./helpers/response-headers/build-api-unauthorized-headers.js";
|
|
37
|
+
export * from "./helpers/response-headers/environment.types.js";
|
|
38
|
+
export * from "./helpers/response-headers/resolve-environment.js";
|
|
39
39
|
|
|
40
40
|
// Utilities
|
|
41
|
-
export * from "./normalization/normalize-list";
|
|
42
|
-
export * from "./normalization/normalize-location";
|
|
41
|
+
export * from "./normalization/normalize-list.js";
|
|
42
|
+
export * from "./normalization/normalize-location.js";
|
|
@@ -3,41 +3,45 @@ import { describe, it, expect } from "vitest";
|
|
|
3
3
|
import { normalizeStringList } from "./normalize-list";
|
|
4
4
|
|
|
5
5
|
describe("normalizeStringList", () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
describe("Happy Path", () => {
|
|
7
|
+
it("lowercases, removes duplicates, and sorts values with proper umlaut handling", () => {
|
|
8
|
+
const input = [
|
|
9
|
+
"Banana",
|
|
10
|
+
"apple",
|
|
11
|
+
"cherry",
|
|
12
|
+
"Äpfel",
|
|
13
|
+
" banana ",
|
|
14
|
+
"Öl",
|
|
15
|
+
"orange",
|
|
16
|
+
];
|
|
17
|
+
const result = normalizeStringList(input);
|
|
18
|
+
expect(result).toEqual([
|
|
19
|
+
"äpfel",
|
|
20
|
+
"apple",
|
|
21
|
+
"banana",
|
|
22
|
+
"cherry",
|
|
23
|
+
"öl",
|
|
24
|
+
"orange",
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
it("handles empty and already normalized inputs", () => {
|
|
29
|
+
expect(normalizeStringList([])).toEqual([]);
|
|
30
|
+
expect(normalizeStringList(["a", "b"])).toEqual(["a", "b"]);
|
|
31
|
+
});
|
|
30
32
|
});
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
describe("Edge Cases", () => {
|
|
35
|
+
it("removes duplicates correctly", () => {
|
|
36
|
+
const input = ["apple", "Apple", "APPLE", " apple ", "banana"];
|
|
37
|
+
const result = normalizeStringList(input);
|
|
38
|
+
expect(result).toEqual(["apple", "banana"]);
|
|
39
|
+
});
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
it("sorts umlauts near their base letters", () => {
|
|
42
|
+
const input = ["z", "ä", "a", "ö", "o", "ü", "u", "b"];
|
|
43
|
+
const result = normalizeStringList(input);
|
|
44
|
+
expect(result).toEqual(["a", "ä", "b", "o", "ö", "u", "ü", "z"]);
|
|
45
|
+
});
|
|
42
46
|
});
|
|
43
47
|
});
|
|
@@ -3,7 +3,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
3
3
|
import { normalizeLocation } from './normalize-location';
|
|
4
4
|
|
|
5
5
|
describe('normalizeLocation', () => {
|
|
6
|
-
describe('space normalization', () => {
|
|
6
|
+
describe('Happy Path - space normalization', () => {
|
|
7
7
|
it('should collapse multiple spaces to single space', () => {
|
|
8
8
|
expect(normalizeLocation('Schlatkow 17390 Schmatzin')).toBe('Schlatkow 17390 Schmatzin');
|
|
9
9
|
expect(normalizeLocation('Berlin Germany')).toBe('Berlin Germany');
|
|
@@ -3,44 +3,50 @@ import { describe, it, expect } from "vitest";
|
|
|
3
3
|
import { hasValidDecimalPrecision } from "./coordinate-precision";
|
|
4
4
|
|
|
5
5
|
describe("hasValidDecimalPrecision", () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
describe("Happy Path", () => {
|
|
7
|
+
it("should accept values with no decimal places", () => {
|
|
8
|
+
expect(hasValidDecimalPrecision(0)).toBe(true);
|
|
9
|
+
expect(hasValidDecimalPrecision(90)).toBe(true);
|
|
10
|
+
expect(hasValidDecimalPrecision(-180)).toBe(true);
|
|
11
|
+
});
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
it("should accept values with up to 8 decimal places", () => {
|
|
14
|
+
expect(hasValidDecimalPrecision(52.52)).toBe(true); // 2 decimal places
|
|
15
|
+
expect(hasValidDecimalPrecision(40.7128)).toBe(true); // 4 decimal places
|
|
16
|
+
expect(hasValidDecimalPrecision(52.520_008)).toBe(true); // 6 decimal places
|
|
17
|
+
expect(hasValidDecimalPrecision(52.520_008_07)).toBe(true); // 8 decimal places
|
|
18
|
+
});
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
it("should support custom max decimal places", () => {
|
|
21
|
+
expect(hasValidDecimalPrecision(1.234, 3)).toBe(true);
|
|
22
|
+
expect(hasValidDecimalPrecision(1.2345, 3)).toBe(false);
|
|
23
|
+
expect(hasValidDecimalPrecision(1.234_56, 5)).toBe(true);
|
|
24
|
+
expect(hasValidDecimalPrecision(1.234_567, 5)).toBe(false);
|
|
25
|
+
});
|
|
22
26
|
});
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// Numbers with more than 8 decimal places should fail
|
|
30
|
-
expect(hasValidDecimalPrecision(1.234_567_89e-1)).toBe(false); // 0.123456789 = 9 decimals
|
|
28
|
+
describe("Error Handling", () => {
|
|
29
|
+
it("should reject values with more than 8 decimal places", () => {
|
|
30
|
+
expect(hasValidDecimalPrecision(52.520_008_071)).toBe(false); // 9 decimal places
|
|
31
|
+
expect(hasValidDecimalPrecision(40.712_812_345_678)).toBe(false); // 12 decimal places
|
|
32
|
+
});
|
|
31
33
|
});
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
describe("Edge Cases", () => {
|
|
36
|
+
it("should handle scientific notation correctly", () => {
|
|
37
|
+
// Small numbers with few actual decimal places should pass
|
|
38
|
+
expect(hasValidDecimalPrecision(1.23e-4)).toBe(true); // 0.000123 = 6 decimals
|
|
39
|
+
expect(hasValidDecimalPrecision(1.23e-2)).toBe(true); // 0.0123 = 4 decimals
|
|
40
|
+
|
|
41
|
+
// Numbers with more than 8 decimal places should fail
|
|
42
|
+
expect(hasValidDecimalPrecision(1.234_567_89e-1)).toBe(false); // 0.123456789 = 9 decimals
|
|
43
|
+
});
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
it("should handle edge cases", () => {
|
|
46
|
+
expect(hasValidDecimalPrecision(0)).toBe(true);
|
|
47
|
+
expect(hasValidDecimalPrecision(-0)).toBe(true);
|
|
48
|
+
// Number.EPSILON has many decimal places when converted to standard notation
|
|
49
|
+
expect(hasValidDecimalPrecision(Number.EPSILON)).toBe(false);
|
|
50
|
+
});
|
|
45
51
|
});
|
|
46
52
|
});
|
|
@@ -3,68 +3,72 @@ import { describe, it, expect } from "vitest";
|
|
|
3
3
|
import { GeoPointSchema } from "./geo-point.schema";
|
|
4
4
|
|
|
5
5
|
describe("GeoPointSchema", () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
describe("Happy Path", () => {
|
|
7
|
+
it("should accept valid geo points", () => {
|
|
8
|
+
expect(GeoPointSchema.parse([0, 0])).toEqual([0, 0]);
|
|
9
|
+
expect(GeoPointSchema.parse([52.52, 13.405])).toEqual([52.52, 13.405]); // Berlin
|
|
10
|
+
expect(GeoPointSchema.parse([40.7128, -74.006])).toEqual([40.7128, -74.006]); // New York
|
|
11
|
+
expect(GeoPointSchema.parse([-33.8688, 151.2093])).toEqual([-33.8688, 151.2093]); // Sydney
|
|
12
|
+
});
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
it("should accept boundary values", () => {
|
|
15
|
+
expect(GeoPointSchema.parse([90, 180])).toEqual([90, 180]); // North Pole, Date Line
|
|
16
|
+
expect(GeoPointSchema.parse([-90, -180])).toEqual([-90, -180]); // South Pole, Date Line
|
|
17
|
+
expect(GeoPointSchema.parse([90, -180])).toEqual([90, -180]);
|
|
18
|
+
expect(GeoPointSchema.parse([-90, 180])).toEqual([-90, 180]);
|
|
19
|
+
});
|
|
18
20
|
});
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
describe("Error Handling", () => {
|
|
23
|
+
it("should reject invalid latitude", () => {
|
|
24
|
+
expect(() => GeoPointSchema.parse([91, 0])).toThrow(
|
|
25
|
+
"Latitude must be less than or equal to 90"
|
|
26
|
+
);
|
|
27
|
+
expect(() => GeoPointSchema.parse([-91, 0])).toThrow(
|
|
28
|
+
"Latitude must be greater than or equal to -90"
|
|
29
|
+
);
|
|
30
|
+
expect(() => GeoPointSchema.parse([100, 0])).toThrow(
|
|
31
|
+
"Latitude must be less than or equal to 90"
|
|
32
|
+
);
|
|
33
|
+
});
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
it("should reject invalid longitude", () => {
|
|
36
|
+
expect(() => GeoPointSchema.parse([0, 181])).toThrow(
|
|
37
|
+
"Longitude must be less than or equal to 180"
|
|
38
|
+
);
|
|
39
|
+
expect(() => GeoPointSchema.parse([0, -181])).toThrow(
|
|
40
|
+
"Longitude must be greater than or equal to -180"
|
|
41
|
+
);
|
|
42
|
+
expect(() => GeoPointSchema.parse([0, 360])).toThrow(
|
|
43
|
+
"Longitude must be less than or equal to 180"
|
|
44
|
+
);
|
|
45
|
+
});
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
it("should reject tuples with wrong length", () => {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
expect(() => GeoPointSchema.parse([0] as any)).toThrow();
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
expect(() => GeoPointSchema.parse([0, 0, 0] as any)).toThrow();
|
|
52
|
+
});
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
it("should reject non-tuple values", () => {
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
+
expect(() => GeoPointSchema.parse("0,0" as any)).toThrow();
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
expect(() => GeoPointSchema.parse({ lat: 0, lng: 0 } as any)).toThrow();
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, unicorn/no-null
|
|
60
|
+
expect(() => GeoPointSchema.parse(null as any)).toThrow();
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
expect(() => GeoPointSchema.parse(undefined as any)).toThrow();
|
|
63
|
+
});
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
it("should reject non-numeric coordinates", () => {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
expect(() => GeoPointSchema.parse(["0", "0"] as any)).toThrow();
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
expect(() => GeoPointSchema.parse([0, "0"] as any)).toThrow();
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
expect(() => GeoPointSchema.parse(["0", 0] as any)).toThrow();
|
|
72
|
+
});
|
|
69
73
|
});
|
|
70
74
|
});
|