@api-client/core 0.15.1 → 0.16.0
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/TESTING_READY.md +114 -0
- package/TESTING_SETUP.md +198 -0
- package/build/src/modeling/Semantics.d.ts +126 -2
- package/build/src/modeling/Semantics.d.ts.map +1 -1
- package/build/src/modeling/Semantics.js +281 -13
- package/build/src/modeling/Semantics.js.map +1 -1
- package/build/src/modeling/definitions/Calculated.d.ts +54 -0
- package/build/src/modeling/definitions/Calculated.d.ts.map +1 -0
- package/build/src/modeling/definitions/Calculated.js +31 -0
- package/build/src/modeling/definitions/Calculated.js.map +1 -0
- package/build/src/modeling/definitions/Categories.d.ts +60 -0
- package/build/src/modeling/definitions/Categories.d.ts.map +1 -0
- package/build/src/modeling/definitions/Categories.js +33 -0
- package/build/src/modeling/definitions/Categories.js.map +1 -0
- package/build/src/modeling/definitions/Derived.d.ts +54 -0
- package/build/src/modeling/definitions/Derived.d.ts.map +1 -0
- package/build/src/modeling/definitions/Derived.js +31 -0
- package/build/src/modeling/definitions/Derived.js.map +1 -0
- package/build/src/modeling/definitions/Description.d.ts +36 -0
- package/build/src/modeling/definitions/Description.d.ts.map +1 -0
- package/build/src/modeling/definitions/Description.js +28 -0
- package/build/src/modeling/definitions/Description.js.map +1 -0
- package/build/src/modeling/definitions/Email.d.ts +66 -0
- package/build/src/modeling/definitions/Email.d.ts.map +1 -0
- package/build/src/modeling/definitions/Email.js +33 -0
- package/build/src/modeling/definitions/Email.js.map +1 -0
- package/build/src/modeling/definitions/GeospatialCoordinates.d.ts +212 -0
- package/build/src/modeling/definitions/GeospatialCoordinates.d.ts.map +1 -0
- package/build/src/modeling/definitions/GeospatialCoordinates.js +129 -0
- package/build/src/modeling/definitions/GeospatialCoordinates.js.map +1 -0
- package/build/src/modeling/definitions/HTML.d.ts +88 -0
- package/build/src/modeling/definitions/HTML.d.ts.map +1 -0
- package/build/src/modeling/definitions/HTML.js +42 -0
- package/build/src/modeling/definitions/HTML.js.map +1 -0
- package/build/src/modeling/definitions/Markdown.d.ts +84 -0
- package/build/src/modeling/definitions/Markdown.d.ts.map +1 -0
- package/build/src/modeling/definitions/Markdown.js +41 -0
- package/build/src/modeling/definitions/Markdown.js.map +1 -0
- package/build/src/modeling/definitions/Password.d.ts +112 -0
- package/build/src/modeling/definitions/Password.d.ts.map +1 -0
- package/build/src/modeling/definitions/Password.js +57 -0
- package/build/src/modeling/definitions/Password.js.map +1 -0
- package/build/src/modeling/definitions/Phone.d.ts +83 -0
- package/build/src/modeling/definitions/Phone.d.ts.map +1 -0
- package/build/src/modeling/definitions/Phone.js +39 -0
- package/build/src/modeling/definitions/Phone.js.map +1 -0
- package/build/src/modeling/definitions/Price.d.ts +102 -0
- package/build/src/modeling/definitions/Price.d.ts.map +1 -0
- package/build/src/modeling/definitions/Price.js +99 -0
- package/build/src/modeling/definitions/Price.js.map +1 -0
- package/build/src/modeling/definitions/PublicUniqueName.d.ts +69 -0
- package/build/src/modeling/definitions/PublicUniqueName.d.ts.map +1 -0
- package/build/src/modeling/definitions/PublicUniqueName.js +34 -0
- package/build/src/modeling/definitions/PublicUniqueName.js.map +1 -0
- package/build/src/modeling/definitions/SKU.d.ts +127 -0
- package/build/src/modeling/definitions/SKU.d.ts.map +1 -0
- package/build/src/modeling/definitions/SKU.js +142 -0
- package/build/src/modeling/definitions/SKU.js.map +1 -0
- package/build/src/modeling/definitions/Status.d.ts +150 -0
- package/build/src/modeling/definitions/Status.d.ts.map +1 -0
- package/build/src/modeling/definitions/Status.js +60 -0
- package/build/src/modeling/definitions/Status.js.map +1 -0
- package/build/src/modeling/definitions/Summary.d.ts +53 -0
- package/build/src/modeling/definitions/Summary.d.ts.map +1 -0
- package/build/src/modeling/definitions/Summary.js +50 -0
- package/build/src/modeling/definitions/Summary.js.map +1 -0
- package/build/src/modeling/definitions/Tags.d.ts +52 -0
- package/build/src/modeling/definitions/Tags.d.ts.map +1 -0
- package/build/src/modeling/definitions/Tags.js +32 -0
- package/build/src/modeling/definitions/Tags.js.map +1 -0
- package/build/src/modeling/definitions/URL.d.ts +68 -0
- package/build/src/modeling/definitions/URL.d.ts.map +1 -0
- package/build/src/modeling/definitions/URL.js +37 -0
- package/build/src/modeling/definitions/URL.js.map +1 -0
- package/build/src/modeling/validation/semantic_validation.d.ts +4 -0
- package/build/src/modeling/validation/semantic_validation.d.ts.map +1 -1
- package/build/src/modeling/validation/semantic_validation.js +32 -1
- package/build/src/modeling/validation/semantic_validation.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +11 -11
- package/package.json +1 -1
- package/src/modeling/Semantics.ts +297 -14
- package/src/modeling/definitions/Calculated.ts +76 -0
- package/src/modeling/definitions/Categories.ts +84 -0
- package/src/modeling/definitions/Derived.ts +76 -0
- package/src/modeling/definitions/Description.ts +55 -0
- package/src/modeling/definitions/Email.ts +90 -0
- package/src/modeling/definitions/GeospatialCoordinates.ts +274 -0
- package/src/modeling/definitions/HTML.ts +121 -0
- package/src/modeling/definitions/Markdown.ts +116 -0
- package/src/modeling/definitions/Password.ts +156 -0
- package/src/modeling/definitions/Phone.ts +116 -0
- package/src/modeling/definitions/Price.examples.md +158 -0
- package/src/modeling/definitions/Price.ts +180 -0
- package/src/modeling/definitions/PublicUniqueName.ts +98 -0
- package/src/modeling/definitions/SKU.examples.md +230 -0
- package/src/modeling/definitions/SKU.ts +254 -0
- package/src/modeling/definitions/Status.ts +227 -0
- package/src/modeling/definitions/Summary.ts +73 -0
- package/src/modeling/definitions/Tags.ts +75 -0
- package/src/modeling/definitions/URL.ts +96 -0
- package/src/modeling/validation/semantic_validation.ts +35 -1
- package/tests/example-test-setup.ts +133 -0
- package/tests/template-node.spec.ts +75 -0
- package/tests/test-utils.ts +293 -0
- package/tests/unit/modeling/definitions/calculated.spec.ts +33 -0
- package/tests/unit/modeling/definitions/categories.spec.ts +38 -0
- package/tests/unit/modeling/definitions/derived.spec.ts +34 -0
- package/tests/unit/modeling/definitions/description.spec.ts +38 -0
- package/tests/unit/modeling/definitions/email.spec.ts +38 -0
- package/tests/unit/modeling/definitions/geospatial-coordinates.spec.ts +41 -0
- package/tests/unit/modeling/definitions/html.spec.ts +38 -0
- package/tests/unit/modeling/definitions/markdown.spec.ts +38 -0
- package/tests/unit/modeling/definitions/password.spec.ts +347 -0
- package/tests/unit/modeling/definitions/phone.spec.ts +38 -0
- package/tests/unit/modeling/definitions/price.spec.ts +465 -0
- package/tests/unit/modeling/definitions/public-unique-name.spec.ts +38 -0
- package/tests/unit/modeling/definitions/sku.spec.ts +240 -0
- package/tests/unit/modeling/definitions/status.spec.ts +37 -0
- package/tests/unit/modeling/definitions/summary.spec.ts +36 -0
- package/tests/unit/modeling/definitions/tags.spec.ts +38 -0
- package/tests/unit/modeling/definitions/url.spec.ts +38 -0
- package/tests/unit/modeling/domain_property.spec.ts +106 -0
- package/tests/unit/modeling/domain_validation.spec.ts +5 -5
- package/tests/unit/modeling/semantic-configs.spec.ts +569 -0
- package/tests/unit/modeling/semantics.spec.ts +52 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createCategoriesSemantic,
|
|
4
|
+
isCategoriesSemantic,
|
|
5
|
+
DEFAULT_CATEGORIES_CONFIG,
|
|
6
|
+
type CategoriesConfig,
|
|
7
|
+
} from '../../../../src/modeling/definitions/Categories.js'
|
|
8
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
9
|
+
|
|
10
|
+
test.group('Categories Semantic Configuration', () => {
|
|
11
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
12
|
+
const semantic = createCategoriesSemantic()
|
|
13
|
+
assert.equal(semantic.id, SemanticType.Categories)
|
|
14
|
+
assert.deepEqual(semantic.config, DEFAULT_CATEGORIES_CONFIG)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
18
|
+
const config: CategoriesConfig = {
|
|
19
|
+
maxCategories: 5,
|
|
20
|
+
allowMultiple: true,
|
|
21
|
+
predefinedCategories: ['feature', 'bug', 'enhancement'],
|
|
22
|
+
}
|
|
23
|
+
const semantic = createCategoriesSemantic(config)
|
|
24
|
+
assert.equal(semantic.id, SemanticType.Categories)
|
|
25
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_CATEGORIES_CONFIG, ...config })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should identify categories semantic', ({ assert }) => {
|
|
29
|
+
const semantic = createCategoriesSemantic()
|
|
30
|
+
assert.isTrue(isCategoriesSemantic(semantic))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should not identify non-categories semantic', ({ assert }) => {
|
|
34
|
+
// Simulate a different semantic
|
|
35
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Email, config: {} }
|
|
36
|
+
assert.isFalse(isCategoriesSemantic(fakeSemantic))
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createDerivedSemantic,
|
|
4
|
+
isDerivedSemantic,
|
|
5
|
+
type DerivedConfig,
|
|
6
|
+
} from '../../../../src/modeling/definitions/Derived.js'
|
|
7
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
8
|
+
|
|
9
|
+
test.group('Derived Semantic Configuration', () => {
|
|
10
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
11
|
+
const config: DerivedConfig = {
|
|
12
|
+
sourceFields: ['firstName', 'lastName'],
|
|
13
|
+
derivationRule: 'concat',
|
|
14
|
+
updateOnChange: true,
|
|
15
|
+
allowManualOverride: true,
|
|
16
|
+
recalculateOnUpdate: false,
|
|
17
|
+
}
|
|
18
|
+
const semantic = createDerivedSemantic(config)
|
|
19
|
+
assert.equal(semantic.id, SemanticType.Derived)
|
|
20
|
+
assert.deepEqual(semantic.config, config)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('should identify derived semantic', ({ assert }) => {
|
|
24
|
+
const config: DerivedConfig = { sourceFields: ['firstName', 'lastName'] }
|
|
25
|
+
const semantic = createDerivedSemantic(config)
|
|
26
|
+
assert.isTrue(isDerivedSemantic(semantic))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('should not identify non-derived semantic', ({ assert }) => {
|
|
30
|
+
// Simulate a different semantic
|
|
31
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Email, config: {} }
|
|
32
|
+
assert.isFalse(isDerivedSemantic(fakeSemantic))
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createDescriptionSemantic,
|
|
4
|
+
isDescriptionSemantic,
|
|
5
|
+
DEFAULT_DESCRIPTION_CONFIG,
|
|
6
|
+
type DescriptionConfig,
|
|
7
|
+
} from '../../../../src/modeling/definitions/Description.js'
|
|
8
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
9
|
+
|
|
10
|
+
test.group('Description Semantic Configuration', () => {
|
|
11
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
12
|
+
const semantic = createDescriptionSemantic()
|
|
13
|
+
assert.equal(semantic.id, SemanticType.Description)
|
|
14
|
+
assert.deepEqual(semantic.config, DEFAULT_DESCRIPTION_CONFIG)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
18
|
+
const config: DescriptionConfig = {
|
|
19
|
+
minLength: 10,
|
|
20
|
+
maxLength: 1000,
|
|
21
|
+
allowMarkdown: true,
|
|
22
|
+
}
|
|
23
|
+
const semantic = createDescriptionSemantic(config)
|
|
24
|
+
assert.equal(semantic.id, SemanticType.Description)
|
|
25
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_DESCRIPTION_CONFIG, ...config })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should identify description semantic', ({ assert }) => {
|
|
29
|
+
const semantic = createDescriptionSemantic()
|
|
30
|
+
assert.isTrue(isDescriptionSemantic(semantic))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should not identify non-description semantic', ({ assert }) => {
|
|
34
|
+
// Simulate a different semantic
|
|
35
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Email, config: {} }
|
|
36
|
+
assert.isFalse(isDescriptionSemantic(fakeSemantic))
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createEmailSemantic,
|
|
4
|
+
isEmailSemantic,
|
|
5
|
+
DEFAULT_EMAIL_CONFIG,
|
|
6
|
+
type EmailConfig,
|
|
7
|
+
} from '../../../../src/modeling/definitions/Email.js'
|
|
8
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
9
|
+
|
|
10
|
+
test.group('Email Semantic Configuration', () => {
|
|
11
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
12
|
+
const semantic = createEmailSemantic()
|
|
13
|
+
assert.equal(semantic.id, SemanticType.Email)
|
|
14
|
+
assert.deepEqual(semantic.config, DEFAULT_EMAIL_CONFIG)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
18
|
+
const config: EmailConfig = {
|
|
19
|
+
allowedDomains: ['example.com'],
|
|
20
|
+
requireVerification: true,
|
|
21
|
+
verificationMethod: 'email',
|
|
22
|
+
}
|
|
23
|
+
const semantic = createEmailSemantic(config)
|
|
24
|
+
assert.equal(semantic.id, SemanticType.Email)
|
|
25
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_EMAIL_CONFIG, ...config })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should identify email semantic', ({ assert }) => {
|
|
29
|
+
const semantic = createEmailSemantic()
|
|
30
|
+
assert.isTrue(isEmailSemantic(semantic))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should not identify non-email semantic', ({ assert }) => {
|
|
34
|
+
// Simulate a different semantic
|
|
35
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Phone, config: {} }
|
|
36
|
+
assert.isFalse(isEmailSemantic(fakeSemantic))
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createGeospatialCoordinatesSemantic,
|
|
4
|
+
isGeospatialCoordinatesSemantic,
|
|
5
|
+
DEFAULT_GEOSPATIAL_CONFIG,
|
|
6
|
+
type GeospatialCoordinatesConfig,
|
|
7
|
+
GeospatialCoordinateFormat,
|
|
8
|
+
GeospatialDistanceUnit,
|
|
9
|
+
GeospatialSpatialReferenceSystem,
|
|
10
|
+
} from '../../../../src/modeling/definitions/GeospatialCoordinates.js'
|
|
11
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
12
|
+
|
|
13
|
+
test.group('GeospatialCoordinates Semantic Configuration', () => {
|
|
14
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
15
|
+
const semantic = createGeospatialCoordinatesSemantic()
|
|
16
|
+
assert.equal(semantic.id, SemanticType.GeospatialCoordinates)
|
|
17
|
+
assert.deepEqual(semantic.config, DEFAULT_GEOSPATIAL_CONFIG)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
21
|
+
const config: GeospatialCoordinatesConfig = {
|
|
22
|
+
format: GeospatialCoordinateFormat.LatLon,
|
|
23
|
+
defaultDistanceUnit: GeospatialDistanceUnit.Kilometers,
|
|
24
|
+
spatialReferenceSystem: GeospatialSpatialReferenceSystem.WGS84,
|
|
25
|
+
}
|
|
26
|
+
const semantic = createGeospatialCoordinatesSemantic(config)
|
|
27
|
+
assert.equal(semantic.id, SemanticType.GeospatialCoordinates)
|
|
28
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_GEOSPATIAL_CONFIG, ...config })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('should identify geospatial semantic', ({ assert }) => {
|
|
32
|
+
const semantic = createGeospatialCoordinatesSemantic()
|
|
33
|
+
assert.isTrue(isGeospatialCoordinatesSemantic(semantic))
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('should not identify non-geospatial semantic', ({ assert }) => {
|
|
37
|
+
// Simulate a different semantic
|
|
38
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Email, config: {} }
|
|
39
|
+
assert.isFalse(isGeospatialCoordinatesSemantic(fakeSemantic))
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createHTMLSemantic,
|
|
4
|
+
isHTMLSemantic,
|
|
5
|
+
DEFAULT_HTML_CONFIG,
|
|
6
|
+
type HTMLConfig,
|
|
7
|
+
} from '../../../../src/modeling/definitions/HTML.js'
|
|
8
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
9
|
+
|
|
10
|
+
test.group('HTML Semantic Configuration', () => {
|
|
11
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
12
|
+
const semantic = createHTMLSemantic()
|
|
13
|
+
assert.equal(semantic.id, SemanticType.HTML)
|
|
14
|
+
assert.deepEqual(semantic.config, DEFAULT_HTML_CONFIG)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
18
|
+
const config: HTMLConfig = {
|
|
19
|
+
renderAsHTML: true,
|
|
20
|
+
allowedTags: ['p', 'div', 'span'],
|
|
21
|
+
sanitize: true,
|
|
22
|
+
}
|
|
23
|
+
const semantic = createHTMLSemantic(config)
|
|
24
|
+
assert.equal(semantic.id, SemanticType.HTML)
|
|
25
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_HTML_CONFIG, ...config })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should identify HTML semantic', ({ assert }) => {
|
|
29
|
+
const semantic = createHTMLSemantic()
|
|
30
|
+
assert.isTrue(isHTMLSemantic(semantic))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should not identify non-HTML semantic', ({ assert }) => {
|
|
34
|
+
// Simulate a different semantic
|
|
35
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Markdown, config: {} }
|
|
36
|
+
assert.isFalse(isHTMLSemantic(fakeSemantic))
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createMarkdownSemantic,
|
|
4
|
+
isMarkdownSemantic,
|
|
5
|
+
DEFAULT_MARKDOWN_CONFIG,
|
|
6
|
+
type MarkdownConfig,
|
|
7
|
+
} from '../../../../src/modeling/definitions/Markdown.js'
|
|
8
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
9
|
+
|
|
10
|
+
test.group('Markdown Semantic Configuration', () => {
|
|
11
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
12
|
+
const semantic = createMarkdownSemantic()
|
|
13
|
+
assert.equal(semantic.id, SemanticType.Markdown)
|
|
14
|
+
assert.deepEqual(semantic.config, DEFAULT_MARKDOWN_CONFIG)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
18
|
+
const config: MarkdownConfig = {
|
|
19
|
+
renderToHTML: true,
|
|
20
|
+
allowedTags: ['p', 'h1', 'h2'],
|
|
21
|
+
sanitize: true,
|
|
22
|
+
}
|
|
23
|
+
const semantic = createMarkdownSemantic(config)
|
|
24
|
+
assert.equal(semantic.id, SemanticType.Markdown)
|
|
25
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_MARKDOWN_CONFIG, ...config })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should identify markdown semantic', ({ assert }) => {
|
|
29
|
+
const semantic = createMarkdownSemantic()
|
|
30
|
+
assert.isTrue(isMarkdownSemantic(semantic))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should not identify non-markdown semantic', ({ assert }) => {
|
|
34
|
+
// Simulate a different semantic
|
|
35
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Email, config: {} }
|
|
36
|
+
assert.isFalse(isMarkdownSemantic(fakeSemantic))
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createPasswordSemantic,
|
|
4
|
+
isPasswordSemantic,
|
|
5
|
+
DEFAULT_PASSWORD_CONFIG,
|
|
6
|
+
PasswordEncryptionAlgorithm,
|
|
7
|
+
type PasswordConfig,
|
|
8
|
+
} from '../../../../src/modeling/definitions/Password.js'
|
|
9
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
10
|
+
|
|
11
|
+
test.group('Password Semantic Configuration', () => {
|
|
12
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
13
|
+
const semantic = createPasswordSemantic()
|
|
14
|
+
assert.equal(semantic.id, SemanticType.Password)
|
|
15
|
+
assert.deepEqual(semantic.config, DEFAULT_PASSWORD_CONFIG)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
19
|
+
const config: PasswordConfig = {
|
|
20
|
+
requireSpecialChars: false,
|
|
21
|
+
requireNumbers: false,
|
|
22
|
+
maxAge: 90,
|
|
23
|
+
preventReuse: true,
|
|
24
|
+
encryptionAlgorithm: PasswordEncryptionAlgorithm.Argon2,
|
|
25
|
+
saltRounds: 16,
|
|
26
|
+
}
|
|
27
|
+
const semantic = createPasswordSemantic(config)
|
|
28
|
+
assert.equal(semantic.id, SemanticType.Password)
|
|
29
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_PASSWORD_CONFIG, ...config })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('should merge metadata properly', ({ assert }) => {
|
|
33
|
+
const config: PasswordConfig = {
|
|
34
|
+
metadata: {
|
|
35
|
+
customField: 'value',
|
|
36
|
+
anotherField: 123,
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
const semantic = createPasswordSemantic(config)
|
|
40
|
+
assert.equal(semantic.id, SemanticType.Password)
|
|
41
|
+
assert.deepEqual(semantic.config?.metadata, config.metadata)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('should create semantic with empty config', ({ assert }) => {
|
|
45
|
+
const semantic = createPasswordSemantic({})
|
|
46
|
+
assert.equal(semantic.id, SemanticType.Password)
|
|
47
|
+
assert.deepEqual(semantic.config, DEFAULT_PASSWORD_CONFIG)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('should preserve custom properties', ({ assert }) => {
|
|
51
|
+
const config: PasswordConfig = {
|
|
52
|
+
customPattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$',
|
|
53
|
+
customErrorMessage: 'Password must be at least 8 characters',
|
|
54
|
+
customProperty: 'custom value',
|
|
55
|
+
}
|
|
56
|
+
const semantic = createPasswordSemantic(config)
|
|
57
|
+
assert.equal(semantic.config?.customPattern, config.customPattern)
|
|
58
|
+
assert.equal(semantic.config?.customErrorMessage, config.customErrorMessage)
|
|
59
|
+
assert.equal(semantic.config?.customProperty, 'custom value')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('should identify password semantic', ({ assert }) => {
|
|
63
|
+
const semantic = createPasswordSemantic()
|
|
64
|
+
assert.isTrue(isPasswordSemantic(semantic))
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('should not identify non-password semantic', ({ assert }) => {
|
|
68
|
+
const fakeSemantic: AppliedDataSemantic = {
|
|
69
|
+
id: SemanticType.Email,
|
|
70
|
+
config: {},
|
|
71
|
+
}
|
|
72
|
+
assert.isFalse(isPasswordSemantic(fakeSemantic))
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('DEFAULT_PASSWORD_CONFIG should have correct values', ({ assert }) => {
|
|
76
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.requireSpecialChars, true)
|
|
77
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.requireNumbers, true)
|
|
78
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.requireUppercase, true)
|
|
79
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.requireLowercase, true)
|
|
80
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.encryptionAlgorithm, PasswordEncryptionAlgorithm.Bcrypt)
|
|
81
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.saltRounds, 12)
|
|
82
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.maxAge, undefined)
|
|
83
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.preventReuse, false)
|
|
84
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.preventReuseCount, 5)
|
|
85
|
+
assert.equal(DEFAULT_PASSWORD_CONFIG.allowCommonPasswords, false)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test.group('Password Encryption Algorithms', () => {
|
|
90
|
+
test('should support bcrypt algorithm', ({ assert }) => {
|
|
91
|
+
const config: PasswordConfig = {
|
|
92
|
+
encryptionAlgorithm: PasswordEncryptionAlgorithm.Bcrypt,
|
|
93
|
+
saltRounds: 10,
|
|
94
|
+
}
|
|
95
|
+
const semantic = createPasswordSemantic(config)
|
|
96
|
+
assert.equal(semantic.config?.encryptionAlgorithm, PasswordEncryptionAlgorithm.Bcrypt)
|
|
97
|
+
assert.equal(semantic.config?.saltRounds, 10)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('should support argon2 algorithm', ({ assert }) => {
|
|
101
|
+
const config: PasswordConfig = {
|
|
102
|
+
encryptionAlgorithm: PasswordEncryptionAlgorithm.Argon2,
|
|
103
|
+
}
|
|
104
|
+
const semantic = createPasswordSemantic(config)
|
|
105
|
+
assert.equal(semantic.config?.encryptionAlgorithm, PasswordEncryptionAlgorithm.Argon2)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('should support scrypt algorithm', ({ assert }) => {
|
|
109
|
+
const config: PasswordConfig = {
|
|
110
|
+
encryptionAlgorithm: PasswordEncryptionAlgorithm.Scrypt,
|
|
111
|
+
}
|
|
112
|
+
const semantic = createPasswordSemantic(config)
|
|
113
|
+
assert.equal(semantic.config?.encryptionAlgorithm, PasswordEncryptionAlgorithm.Scrypt)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('PasswordEncryptionAlgorithm enum should have correct values', ({ assert }) => {
|
|
117
|
+
assert.equal(PasswordEncryptionAlgorithm.Bcrypt, 'bcrypt')
|
|
118
|
+
assert.equal(PasswordEncryptionAlgorithm.Argon2, 'argon2')
|
|
119
|
+
assert.equal(PasswordEncryptionAlgorithm.Scrypt, 'scrypt')
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test.group('Password Validation Requirements', () => {
|
|
124
|
+
test('should configure character requirements', ({ assert }) => {
|
|
125
|
+
const config: PasswordConfig = {
|
|
126
|
+
requireSpecialChars: true,
|
|
127
|
+
requireNumbers: true,
|
|
128
|
+
requireUppercase: false,
|
|
129
|
+
requireLowercase: false,
|
|
130
|
+
}
|
|
131
|
+
const semantic = createPasswordSemantic(config)
|
|
132
|
+
assert.equal(semantic.config?.requireSpecialChars, true)
|
|
133
|
+
assert.equal(semantic.config?.requireNumbers, true)
|
|
134
|
+
assert.equal(semantic.config?.requireUppercase, false)
|
|
135
|
+
assert.equal(semantic.config?.requireLowercase, false)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('should configure custom pattern', ({ assert }) => {
|
|
139
|
+
const customPattern = '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$'
|
|
140
|
+
const config: PasswordConfig = {
|
|
141
|
+
customPattern,
|
|
142
|
+
customErrorMessage: 'Invalid password format',
|
|
143
|
+
}
|
|
144
|
+
const semantic = createPasswordSemantic(config)
|
|
145
|
+
assert.equal(semantic.config?.customPattern, customPattern)
|
|
146
|
+
assert.equal(semantic.config?.customErrorMessage, 'Invalid password format')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('should configure common password policy', ({ assert }) => {
|
|
150
|
+
const config: PasswordConfig = {
|
|
151
|
+
allowCommonPasswords: true,
|
|
152
|
+
}
|
|
153
|
+
const semantic = createPasswordSemantic(config)
|
|
154
|
+
assert.equal(semantic.config?.allowCommonPasswords, true)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test.group('Password Age and Reuse Policies', () => {
|
|
159
|
+
test('should configure password expiration', ({ assert }) => {
|
|
160
|
+
const config: PasswordConfig = {
|
|
161
|
+
maxAge: 30,
|
|
162
|
+
}
|
|
163
|
+
const semantic = createPasswordSemantic(config)
|
|
164
|
+
assert.equal(semantic.config?.maxAge, 30)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('should configure password reuse prevention', ({ assert }) => {
|
|
168
|
+
const config: PasswordConfig = {
|
|
169
|
+
preventReuse: true,
|
|
170
|
+
preventReuseCount: 10,
|
|
171
|
+
}
|
|
172
|
+
const semantic = createPasswordSemantic(config)
|
|
173
|
+
assert.equal(semantic.config?.preventReuse, true)
|
|
174
|
+
assert.equal(semantic.config?.preventReuseCount, 10)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('should handle undefined maxAge', ({ assert }) => {
|
|
178
|
+
const config: PasswordConfig = {
|
|
179
|
+
maxAge: undefined,
|
|
180
|
+
}
|
|
181
|
+
const semantic = createPasswordSemantic(config)
|
|
182
|
+
assert.equal(semantic.config?.maxAge, undefined)
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test.group('Password Salt Rounds Configuration', () => {
|
|
187
|
+
test('should configure salt rounds for bcrypt', ({ assert }) => {
|
|
188
|
+
const config: PasswordConfig = {
|
|
189
|
+
encryptionAlgorithm: PasswordEncryptionAlgorithm.Bcrypt,
|
|
190
|
+
saltRounds: 15,
|
|
191
|
+
}
|
|
192
|
+
const semantic = createPasswordSemantic(config)
|
|
193
|
+
assert.equal(semantic.config?.saltRounds, 15)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('should use default salt rounds when not specified', ({ assert }) => {
|
|
197
|
+
const config: PasswordConfig = {
|
|
198
|
+
encryptionAlgorithm: PasswordEncryptionAlgorithm.Bcrypt,
|
|
199
|
+
}
|
|
200
|
+
const semantic = createPasswordSemantic(config)
|
|
201
|
+
assert.equal(semantic.config?.saltRounds, DEFAULT_PASSWORD_CONFIG.saltRounds)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('should handle low salt rounds', ({ assert }) => {
|
|
205
|
+
const config: PasswordConfig = {
|
|
206
|
+
saltRounds: 4,
|
|
207
|
+
}
|
|
208
|
+
const semantic = createPasswordSemantic(config)
|
|
209
|
+
assert.equal(semantic.config?.saltRounds, 4)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('should handle high salt rounds', ({ assert }) => {
|
|
213
|
+
const config: PasswordConfig = {
|
|
214
|
+
saltRounds: 20,
|
|
215
|
+
}
|
|
216
|
+
const semantic = createPasswordSemantic(config)
|
|
217
|
+
assert.equal(semantic.config?.saltRounds, 20)
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test.group('Password Metadata and Custom Properties', () => {
|
|
222
|
+
test('should handle complex metadata', ({ assert }) => {
|
|
223
|
+
const metadata = {
|
|
224
|
+
source: 'user_registration',
|
|
225
|
+
lastUpdated: '2025-06-23T00:00:00Z',
|
|
226
|
+
complexity: {
|
|
227
|
+
score: 85,
|
|
228
|
+
level: 'high',
|
|
229
|
+
},
|
|
230
|
+
tags: ['secure', 'enterprise'],
|
|
231
|
+
}
|
|
232
|
+
const config: PasswordConfig = {
|
|
233
|
+
metadata,
|
|
234
|
+
}
|
|
235
|
+
const semantic = createPasswordSemantic(config)
|
|
236
|
+
assert.deepEqual(semantic.config?.metadata, metadata)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('should preserve metadata when merging configs', ({ assert }) => {
|
|
240
|
+
const metadata = {
|
|
241
|
+
customField: 'value',
|
|
242
|
+
nested: {
|
|
243
|
+
property: 'test',
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
const config: PasswordConfig = {
|
|
247
|
+
requireSpecialChars: false,
|
|
248
|
+
metadata,
|
|
249
|
+
}
|
|
250
|
+
const semantic = createPasswordSemantic(config)
|
|
251
|
+
assert.equal(semantic.config?.requireSpecialChars, false)
|
|
252
|
+
assert.deepEqual(semantic.config?.metadata, metadata)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test('should handle additional properties via index signature', ({ assert }) => {
|
|
256
|
+
const config: PasswordConfig = {
|
|
257
|
+
customValidationFunction: 'validatePasswordStrength',
|
|
258
|
+
applicationSpecific: {
|
|
259
|
+
domain: 'example.com',
|
|
260
|
+
version: '1.0',
|
|
261
|
+
},
|
|
262
|
+
isProduction: true,
|
|
263
|
+
}
|
|
264
|
+
const semantic = createPasswordSemantic(config)
|
|
265
|
+
assert.equal(semantic.config?.customValidationFunction, 'validatePasswordStrength')
|
|
266
|
+
assert.deepEqual(semantic.config?.applicationSpecific, {
|
|
267
|
+
domain: 'example.com',
|
|
268
|
+
version: '1.0',
|
|
269
|
+
})
|
|
270
|
+
assert.equal(semantic.config?.isProduction, true)
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
test.group('Password Configuration Edge Cases', () => {
|
|
275
|
+
test('should handle null config gracefully', ({ assert }) => {
|
|
276
|
+
const semantic = createPasswordSemantic()
|
|
277
|
+
assert.equal(semantic.id, SemanticType.Password)
|
|
278
|
+
assert.isObject(semantic.config)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
test('should override default config completely when specified', ({ assert }) => {
|
|
282
|
+
const config: PasswordConfig = {
|
|
283
|
+
requireSpecialChars: false,
|
|
284
|
+
requireNumbers: false,
|
|
285
|
+
requireUppercase: false,
|
|
286
|
+
requireLowercase: false,
|
|
287
|
+
encryptionAlgorithm: PasswordEncryptionAlgorithm.Scrypt,
|
|
288
|
+
saltRounds: 8,
|
|
289
|
+
maxAge: 365,
|
|
290
|
+
preventReuse: true,
|
|
291
|
+
preventReuseCount: 3,
|
|
292
|
+
allowCommonPasswords: true,
|
|
293
|
+
}
|
|
294
|
+
const semantic = createPasswordSemantic(config)
|
|
295
|
+
|
|
296
|
+
// Verify all properties are overridden
|
|
297
|
+
assert.equal(semantic.config?.requireSpecialChars, false)
|
|
298
|
+
assert.equal(semantic.config?.requireNumbers, false)
|
|
299
|
+
assert.equal(semantic.config?.requireUppercase, false)
|
|
300
|
+
assert.equal(semantic.config?.requireLowercase, false)
|
|
301
|
+
assert.equal(semantic.config?.encryptionAlgorithm, PasswordEncryptionAlgorithm.Scrypt)
|
|
302
|
+
assert.equal(semantic.config?.saltRounds, 8)
|
|
303
|
+
assert.equal(semantic.config?.maxAge, 365)
|
|
304
|
+
assert.equal(semantic.config?.preventReuse, true)
|
|
305
|
+
assert.equal(semantic.config?.preventReuseCount, 3)
|
|
306
|
+
assert.equal(semantic.config?.allowCommonPasswords, true)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test('should handle partial config override', ({ assert }) => {
|
|
310
|
+
const config: PasswordConfig = {
|
|
311
|
+
requireSpecialChars: false,
|
|
312
|
+
maxAge: 60,
|
|
313
|
+
}
|
|
314
|
+
const semantic = createPasswordSemantic(config)
|
|
315
|
+
|
|
316
|
+
// Verify specified properties are overridden
|
|
317
|
+
assert.equal(semantic.config?.requireSpecialChars, false)
|
|
318
|
+
assert.equal(semantic.config?.maxAge, 60)
|
|
319
|
+
|
|
320
|
+
// Verify unspecified properties use defaults
|
|
321
|
+
assert.equal(semantic.config?.requireNumbers, DEFAULT_PASSWORD_CONFIG.requireNumbers)
|
|
322
|
+
assert.equal(semantic.config?.encryptionAlgorithm, DEFAULT_PASSWORD_CONFIG.encryptionAlgorithm)
|
|
323
|
+
assert.equal(semantic.config?.saltRounds, DEFAULT_PASSWORD_CONFIG.saltRounds)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test('should handle zero values correctly', ({ assert }) => {
|
|
327
|
+
const config: PasswordConfig = {
|
|
328
|
+
saltRounds: 0,
|
|
329
|
+
maxAge: 0,
|
|
330
|
+
preventReuseCount: 0,
|
|
331
|
+
}
|
|
332
|
+
const semantic = createPasswordSemantic(config)
|
|
333
|
+
assert.equal(semantic.config?.saltRounds, 0)
|
|
334
|
+
assert.equal(semantic.config?.maxAge, 0)
|
|
335
|
+
assert.equal(semantic.config?.preventReuseCount, 0)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test('should handle empty string values', ({ assert }) => {
|
|
339
|
+
const config: PasswordConfig = {
|
|
340
|
+
customPattern: '',
|
|
341
|
+
customErrorMessage: '',
|
|
342
|
+
}
|
|
343
|
+
const semantic = createPasswordSemantic(config)
|
|
344
|
+
assert.equal(semantic.config?.customPattern, '')
|
|
345
|
+
assert.equal(semantic.config?.customErrorMessage, '')
|
|
346
|
+
})
|
|
347
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createPhoneSemantic,
|
|
4
|
+
isPhoneSemantic,
|
|
5
|
+
DEFAULT_PHONE_CONFIG,
|
|
6
|
+
type PhoneConfig,
|
|
7
|
+
} from '../../../../src/modeling/definitions/Phone.js'
|
|
8
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
9
|
+
|
|
10
|
+
test.group('Phone Semantic Configuration', () => {
|
|
11
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
12
|
+
const semantic = createPhoneSemantic()
|
|
13
|
+
assert.equal(semantic.id, SemanticType.Phone)
|
|
14
|
+
assert.deepEqual(semantic.config, DEFAULT_PHONE_CONFIG)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
18
|
+
const config: PhoneConfig = {
|
|
19
|
+
allowedCountries: ['US', 'CA'],
|
|
20
|
+
format: 'E.164',
|
|
21
|
+
requireVerification: true,
|
|
22
|
+
}
|
|
23
|
+
const semantic = createPhoneSemantic(config)
|
|
24
|
+
assert.equal(semantic.id, SemanticType.Phone)
|
|
25
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_PHONE_CONFIG, ...config })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should identify phone semantic', ({ assert }) => {
|
|
29
|
+
const semantic = createPhoneSemantic()
|
|
30
|
+
assert.isTrue(isPhoneSemantic(semantic))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should not identify non-phone semantic', ({ assert }) => {
|
|
34
|
+
// Simulate a different semantic
|
|
35
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Email, config: {} }
|
|
36
|
+
assert.isFalse(isPhoneSemantic(fakeSemantic))
|
|
37
|
+
})
|
|
38
|
+
})
|