@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,465 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createPriceSemantic,
|
|
4
|
+
isPriceSemantic,
|
|
5
|
+
validatePriceConfig,
|
|
6
|
+
DEFAULT_PRICE_CONFIG,
|
|
7
|
+
PRICE_PRESETS,
|
|
8
|
+
type PriceConfig,
|
|
9
|
+
} from '../../../../src/modeling/definitions/Price.js'
|
|
10
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
11
|
+
|
|
12
|
+
test.group('Price Semantic Configuration', () => {
|
|
13
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
14
|
+
const semantic = createPriceSemantic()
|
|
15
|
+
assert.equal(semantic.id, SemanticType.Price)
|
|
16
|
+
assert.deepEqual(semantic.config, DEFAULT_PRICE_CONFIG)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
20
|
+
const config: PriceConfig = {
|
|
21
|
+
storageFormat: 'complex_object',
|
|
22
|
+
allowedCurrencies: ['USD', 'EUR'],
|
|
23
|
+
decimalPlaces: 4,
|
|
24
|
+
allowNegative: true,
|
|
25
|
+
validateCurrencyCode: false,
|
|
26
|
+
}
|
|
27
|
+
const semantic = createPriceSemantic(config)
|
|
28
|
+
assert.equal(semantic.id, SemanticType.Price)
|
|
29
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_PRICE_CONFIG, ...config })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('should merge metadata properly', ({ assert }) => {
|
|
33
|
+
const config: PriceConfig = {
|
|
34
|
+
metadata: {
|
|
35
|
+
customField: 'value',
|
|
36
|
+
anotherField: 123,
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
const semantic = createPriceSemantic(config)
|
|
40
|
+
assert.equal(semantic.id, SemanticType.Price)
|
|
41
|
+
assert.deepEqual(semantic.config?.metadata, config.metadata)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('should identify price semantic', ({ assert }) => {
|
|
45
|
+
const semantic = createPriceSemantic()
|
|
46
|
+
assert.isTrue(isPriceSemantic(semantic))
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('should not identify non-price semantic', ({ assert }) => {
|
|
50
|
+
const fakeSemantic: AppliedDataSemantic = {
|
|
51
|
+
id: SemanticType.Email,
|
|
52
|
+
config: {},
|
|
53
|
+
}
|
|
54
|
+
assert.isFalse(isPriceSemantic(fakeSemantic))
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('DEFAULT_PRICE_CONFIG should have correct values', ({ assert }) => {
|
|
58
|
+
assert.equal(DEFAULT_PRICE_CONFIG.storageFormat, 'decimal')
|
|
59
|
+
assert.equal(DEFAULT_PRICE_CONFIG.defaultCurrency, 'USD')
|
|
60
|
+
assert.equal(DEFAULT_PRICE_CONFIG.decimalPlaces, 2)
|
|
61
|
+
assert.equal(DEFAULT_PRICE_CONFIG.allowNegative, false)
|
|
62
|
+
assert.equal(DEFAULT_PRICE_CONFIG.validateCurrencyCode, true)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test.group('Price Storage Formats', () => {
|
|
67
|
+
test('should support decimal storage format', ({ assert }) => {
|
|
68
|
+
const config: PriceConfig = {
|
|
69
|
+
storageFormat: 'decimal',
|
|
70
|
+
defaultCurrency: 'USD',
|
|
71
|
+
decimalPlaces: 2,
|
|
72
|
+
}
|
|
73
|
+
const semantic = createPriceSemantic(config)
|
|
74
|
+
assert.equal(semantic.config?.storageFormat, 'decimal')
|
|
75
|
+
assert.equal(semantic.config?.defaultCurrency, 'USD')
|
|
76
|
+
assert.equal(semantic.config?.decimalPlaces, 2)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('should support integer_cents storage format', ({ assert }) => {
|
|
80
|
+
const config: PriceConfig = {
|
|
81
|
+
storageFormat: 'integer_cents',
|
|
82
|
+
defaultCurrency: 'EUR',
|
|
83
|
+
decimalPlaces: 2,
|
|
84
|
+
}
|
|
85
|
+
const semantic = createPriceSemantic(config)
|
|
86
|
+
assert.equal(semantic.config?.storageFormat, 'integer_cents')
|
|
87
|
+
assert.equal(semantic.config?.defaultCurrency, 'EUR')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('should support complex_object storage format', ({ assert }) => {
|
|
91
|
+
const config: PriceConfig = {
|
|
92
|
+
storageFormat: 'complex_object',
|
|
93
|
+
allowedCurrencies: ['USD', 'EUR', 'GBP'],
|
|
94
|
+
decimalPlaces: 2,
|
|
95
|
+
}
|
|
96
|
+
const semantic = createPriceSemantic(config)
|
|
97
|
+
assert.equal(semantic.config?.storageFormat, 'complex_object')
|
|
98
|
+
assert.deepEqual(semantic.config?.allowedCurrencies, ['USD', 'EUR', 'GBP'])
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test.group('Price Configuration Validation', () => {
|
|
103
|
+
test('should validate that defaultCurrency is required for decimal format', ({ assert }) => {
|
|
104
|
+
const config: PriceConfig = {
|
|
105
|
+
storageFormat: 'decimal',
|
|
106
|
+
// Missing defaultCurrency
|
|
107
|
+
decimalPlaces: 2,
|
|
108
|
+
}
|
|
109
|
+
const errors = validatePriceConfig(config)
|
|
110
|
+
assert.include(errors, 'defaultCurrency is required when storageFormat is not complex_object')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('should validate that defaultCurrency is required for integer_cents format', ({ assert }) => {
|
|
114
|
+
const config: PriceConfig = {
|
|
115
|
+
storageFormat: 'integer_cents',
|
|
116
|
+
// Missing defaultCurrency
|
|
117
|
+
decimalPlaces: 2,
|
|
118
|
+
}
|
|
119
|
+
const errors = validatePriceConfig(config)
|
|
120
|
+
assert.include(errors, 'defaultCurrency is required when storageFormat is not complex_object')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('should not require defaultCurrency for complex_object format', ({ assert }) => {
|
|
124
|
+
const config: PriceConfig = {
|
|
125
|
+
storageFormat: 'complex_object',
|
|
126
|
+
// No defaultCurrency needed
|
|
127
|
+
allowedCurrencies: ['USD', 'EUR'],
|
|
128
|
+
}
|
|
129
|
+
const errors = validatePriceConfig(config)
|
|
130
|
+
assert.notInclude(errors, 'defaultCurrency is required when storageFormat is not complex_object')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('should validate that decimalPlaces is non-negative', ({ assert }) => {
|
|
134
|
+
const config: PriceConfig = {
|
|
135
|
+
storageFormat: 'decimal',
|
|
136
|
+
defaultCurrency: 'USD',
|
|
137
|
+
decimalPlaces: -1,
|
|
138
|
+
}
|
|
139
|
+
const errors = validatePriceConfig(config)
|
|
140
|
+
assert.include(errors, 'decimalPlaces must be non-negative')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('should accept zero decimal places', ({ assert }) => {
|
|
144
|
+
const config: PriceConfig = {
|
|
145
|
+
storageFormat: 'decimal',
|
|
146
|
+
defaultCurrency: 'JPY',
|
|
147
|
+
decimalPlaces: 0,
|
|
148
|
+
}
|
|
149
|
+
const errors = validatePriceConfig(config)
|
|
150
|
+
assert.notInclude(errors, 'decimalPlaces must be non-negative')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('should return empty array for valid config', ({ assert }) => {
|
|
154
|
+
const config: PriceConfig = {
|
|
155
|
+
storageFormat: 'decimal',
|
|
156
|
+
defaultCurrency: 'USD',
|
|
157
|
+
decimalPlaces: 2,
|
|
158
|
+
allowNegative: false,
|
|
159
|
+
}
|
|
160
|
+
const errors = validatePriceConfig(config)
|
|
161
|
+
assert.lengthOf(errors, 0)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('should accumulate multiple validation errors', ({ assert }) => {
|
|
165
|
+
const config: PriceConfig = {
|
|
166
|
+
storageFormat: 'decimal',
|
|
167
|
+
// Missing defaultCurrency
|
|
168
|
+
decimalPlaces: -2, // Invalid decimal places
|
|
169
|
+
}
|
|
170
|
+
const errors = validatePriceConfig(config)
|
|
171
|
+
assert.lengthOf(errors, 2)
|
|
172
|
+
assert.include(errors, 'defaultCurrency is required when storageFormat is not complex_object')
|
|
173
|
+
assert.include(errors, 'decimalPlaces must be non-negative')
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test.group('Price Presets', () => {
|
|
178
|
+
test('USD_DECIMAL preset should have correct configuration', ({ assert }) => {
|
|
179
|
+
const preset = PRICE_PRESETS.USD_DECIMAL
|
|
180
|
+
assert.equal(preset.id, SemanticType.Price)
|
|
181
|
+
assert.equal(preset.config?.storageFormat, 'decimal')
|
|
182
|
+
assert.equal(preset.config?.defaultCurrency, 'USD')
|
|
183
|
+
assert.equal(preset.config?.decimalPlaces, 2)
|
|
184
|
+
assert.equal(preset.config?.allowNegative, false)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('USD_CENTS preset should have correct configuration', ({ assert }) => {
|
|
188
|
+
const preset = PRICE_PRESETS.USD_CENTS
|
|
189
|
+
assert.equal(preset.id, SemanticType.Price)
|
|
190
|
+
assert.equal(preset.config?.storageFormat, 'integer_cents')
|
|
191
|
+
assert.equal(preset.config?.defaultCurrency, 'USD')
|
|
192
|
+
assert.equal(preset.config?.decimalPlaces, 2)
|
|
193
|
+
assert.equal(preset.config?.allowNegative, false)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('MULTI_CURRENCY preset should have correct configuration', ({ assert }) => {
|
|
197
|
+
const preset = PRICE_PRESETS.MULTI_CURRENCY
|
|
198
|
+
assert.equal(preset.id, SemanticType.Price)
|
|
199
|
+
assert.equal(preset.config?.storageFormat, 'complex_object')
|
|
200
|
+
assert.deepEqual(preset.config?.allowedCurrencies, ['USD', 'EUR', 'GBP', 'JPY', 'CAD'])
|
|
201
|
+
assert.equal(preset.config?.decimalPlaces, 2)
|
|
202
|
+
assert.equal(preset.config?.allowNegative, false)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('WITH_NEGATIVES preset should have correct configuration', ({ assert }) => {
|
|
206
|
+
const preset = PRICE_PRESETS.WITH_NEGATIVES
|
|
207
|
+
assert.equal(preset.id, SemanticType.Price)
|
|
208
|
+
assert.equal(preset.config?.storageFormat, 'decimal')
|
|
209
|
+
assert.equal(preset.config?.defaultCurrency, 'USD')
|
|
210
|
+
assert.equal(preset.config?.decimalPlaces, 2)
|
|
211
|
+
assert.equal(preset.config?.allowNegative, true)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test('HIGH_PRECISION preset should have correct configuration', ({ assert }) => {
|
|
215
|
+
const preset = PRICE_PRESETS.HIGH_PRECISION
|
|
216
|
+
assert.equal(preset.id, SemanticType.Price)
|
|
217
|
+
assert.equal(preset.config?.storageFormat, 'decimal')
|
|
218
|
+
assert.equal(preset.config?.defaultCurrency, 'BTC')
|
|
219
|
+
assert.equal(preset.config?.decimalPlaces, 8)
|
|
220
|
+
assert.equal(preset.config?.allowNegative, false)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('all presets should be valid price semantics', ({ assert }) => {
|
|
224
|
+
Object.values(PRICE_PRESETS).forEach((preset) => {
|
|
225
|
+
assert.isTrue(isPriceSemantic(preset))
|
|
226
|
+
// Validate each preset config
|
|
227
|
+
if (preset.config) {
|
|
228
|
+
const errors = validatePriceConfig(preset.config)
|
|
229
|
+
assert.lengthOf(errors, 0, `Preset ${preset.config.storageFormat} should have valid config`)
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test.group('Price Currency Configuration', () => {
|
|
236
|
+
test('should support single currency with defaultCurrency', ({ assert }) => {
|
|
237
|
+
const config: PriceConfig = {
|
|
238
|
+
storageFormat: 'decimal',
|
|
239
|
+
defaultCurrency: 'EUR',
|
|
240
|
+
decimalPlaces: 2,
|
|
241
|
+
}
|
|
242
|
+
const semantic = createPriceSemantic(config)
|
|
243
|
+
assert.equal(semantic.config?.defaultCurrency, 'EUR')
|
|
244
|
+
assert.isUndefined(semantic.config?.allowedCurrencies)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test('should support multiple currencies with allowedCurrencies', ({ assert }) => {
|
|
248
|
+
const currencies = ['USD', 'EUR', 'GBP', 'JPY', 'CAD', 'AUD']
|
|
249
|
+
const config: PriceConfig = {
|
|
250
|
+
storageFormat: 'complex_object',
|
|
251
|
+
allowedCurrencies: currencies,
|
|
252
|
+
decimalPlaces: 2,
|
|
253
|
+
}
|
|
254
|
+
const semantic = createPriceSemantic(config)
|
|
255
|
+
assert.deepEqual(semantic.config?.allowedCurrencies, currencies)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('should support currency validation toggle', ({ assert }) => {
|
|
259
|
+
const configWithValidation: PriceConfig = {
|
|
260
|
+
storageFormat: 'decimal',
|
|
261
|
+
defaultCurrency: 'USD',
|
|
262
|
+
validateCurrencyCode: true,
|
|
263
|
+
}
|
|
264
|
+
const configWithoutValidation: PriceConfig = {
|
|
265
|
+
storageFormat: 'decimal',
|
|
266
|
+
defaultCurrency: 'USD',
|
|
267
|
+
validateCurrencyCode: false,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const semanticWithValidation = createPriceSemantic(configWithValidation)
|
|
271
|
+
const semanticWithoutValidation = createPriceSemantic(configWithoutValidation)
|
|
272
|
+
|
|
273
|
+
assert.isTrue(semanticWithValidation.config?.validateCurrencyCode)
|
|
274
|
+
assert.isFalse(semanticWithoutValidation.config?.validateCurrencyCode)
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test.group('Price Precision Configuration', () => {
|
|
279
|
+
test('should support standard 2 decimal places (USD, EUR)', ({ assert }) => {
|
|
280
|
+
const config: PriceConfig = {
|
|
281
|
+
storageFormat: 'decimal',
|
|
282
|
+
defaultCurrency: 'USD',
|
|
283
|
+
decimalPlaces: 2,
|
|
284
|
+
}
|
|
285
|
+
const semantic = createPriceSemantic(config)
|
|
286
|
+
assert.equal(semantic.config?.decimalPlaces, 2)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('should support zero decimal places (JPY, KRW)', ({ assert }) => {
|
|
290
|
+
const config: PriceConfig = {
|
|
291
|
+
storageFormat: 'decimal',
|
|
292
|
+
defaultCurrency: 'JPY',
|
|
293
|
+
decimalPlaces: 0,
|
|
294
|
+
}
|
|
295
|
+
const semantic = createPriceSemantic(config)
|
|
296
|
+
assert.equal(semantic.config?.decimalPlaces, 0)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test('should support high precision (3 decimal places for BHD, KWD)', ({ assert }) => {
|
|
300
|
+
const config: PriceConfig = {
|
|
301
|
+
storageFormat: 'decimal',
|
|
302
|
+
defaultCurrency: 'BHD',
|
|
303
|
+
decimalPlaces: 3,
|
|
304
|
+
}
|
|
305
|
+
const semantic = createPriceSemantic(config)
|
|
306
|
+
assert.equal(semantic.config?.decimalPlaces, 3)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test('should support very high precision (cryptocurrencies)', ({ assert }) => {
|
|
310
|
+
const config: PriceConfig = {
|
|
311
|
+
storageFormat: 'decimal',
|
|
312
|
+
defaultCurrency: 'BTC',
|
|
313
|
+
decimalPlaces: 8,
|
|
314
|
+
}
|
|
315
|
+
const semantic = createPriceSemantic(config)
|
|
316
|
+
assert.equal(semantic.config?.decimalPlaces, 8)
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
test.group('Price Negative Values Configuration', () => {
|
|
321
|
+
test('should support disallowing negative values (default)', ({ assert }) => {
|
|
322
|
+
const config: PriceConfig = {
|
|
323
|
+
storageFormat: 'decimal',
|
|
324
|
+
defaultCurrency: 'USD',
|
|
325
|
+
allowNegative: false,
|
|
326
|
+
}
|
|
327
|
+
const semantic = createPriceSemantic(config)
|
|
328
|
+
assert.isFalse(semantic.config?.allowNegative)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
test('should support allowing negative values (refunds, discounts)', ({ assert }) => {
|
|
332
|
+
const config: PriceConfig = {
|
|
333
|
+
storageFormat: 'decimal',
|
|
334
|
+
defaultCurrency: 'USD',
|
|
335
|
+
allowNegative: true,
|
|
336
|
+
}
|
|
337
|
+
const semantic = createPriceSemantic(config)
|
|
338
|
+
assert.isTrue(semantic.config?.allowNegative)
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
test.group('Price Metadata Configuration', () => {
|
|
343
|
+
test('should support custom metadata', ({ assert }) => {
|
|
344
|
+
const metadata = {
|
|
345
|
+
displayFormat: 'currency',
|
|
346
|
+
showCurrencySymbol: true,
|
|
347
|
+
rounding: 'standard',
|
|
348
|
+
customField: 'value',
|
|
349
|
+
}
|
|
350
|
+
const config: PriceConfig = {
|
|
351
|
+
storageFormat: 'decimal',
|
|
352
|
+
defaultCurrency: 'USD',
|
|
353
|
+
metadata,
|
|
354
|
+
}
|
|
355
|
+
const semantic = createPriceSemantic(config)
|
|
356
|
+
assert.deepEqual(semantic.config?.metadata, metadata)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
test('should merge metadata with other config', ({ assert }) => {
|
|
360
|
+
const config: PriceConfig = {
|
|
361
|
+
storageFormat: 'complex_object',
|
|
362
|
+
allowedCurrencies: ['USD', 'EUR'],
|
|
363
|
+
decimalPlaces: 2,
|
|
364
|
+
metadata: {
|
|
365
|
+
source: 'external_api',
|
|
366
|
+
lastUpdated: '2023-01-01',
|
|
367
|
+
},
|
|
368
|
+
}
|
|
369
|
+
const semantic = createPriceSemantic(config)
|
|
370
|
+
assert.equal(semantic.config?.storageFormat, 'complex_object')
|
|
371
|
+
assert.deepEqual(semantic.config?.allowedCurrencies, ['USD', 'EUR'])
|
|
372
|
+
assert.equal(semantic.config?.decimalPlaces, 2)
|
|
373
|
+
assert.deepEqual(semantic.config?.metadata, {
|
|
374
|
+
source: 'external_api',
|
|
375
|
+
lastUpdated: '2023-01-01',
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
test.group('Price Cross-semantic Type Guards', () => {
|
|
381
|
+
test('should correctly identify different semantic types', ({ assert }) => {
|
|
382
|
+
const priceSemantic = createPriceSemantic()
|
|
383
|
+
|
|
384
|
+
// Mock other semantics for comparison
|
|
385
|
+
const emailSemantic: AppliedDataSemantic = { id: SemanticType.Email }
|
|
386
|
+
const statusSemantic: AppliedDataSemantic = { id: SemanticType.Status }
|
|
387
|
+
const userSemantic: AppliedDataSemantic = { id: SemanticType.User }
|
|
388
|
+
|
|
389
|
+
// Price semantic should only be identified as Price
|
|
390
|
+
assert.isTrue(isPriceSemantic(priceSemantic))
|
|
391
|
+
assert.isFalse(isPriceSemantic(emailSemantic))
|
|
392
|
+
assert.isFalse(isPriceSemantic(statusSemantic))
|
|
393
|
+
assert.isFalse(isPriceSemantic(userSemantic))
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
test.group('Price Real-world Use Cases', () => {
|
|
398
|
+
test('should configure for simple e-commerce store', ({ assert }) => {
|
|
399
|
+
const config: PriceConfig = {
|
|
400
|
+
storageFormat: 'decimal',
|
|
401
|
+
defaultCurrency: 'USD',
|
|
402
|
+
decimalPlaces: 2,
|
|
403
|
+
allowNegative: false,
|
|
404
|
+
validateCurrencyCode: true,
|
|
405
|
+
}
|
|
406
|
+
const semantic = createPriceSemantic(config)
|
|
407
|
+
const errors = validatePriceConfig(config)
|
|
408
|
+
|
|
409
|
+
assert.lengthOf(errors, 0)
|
|
410
|
+
assert.equal(semantic.config?.storageFormat, 'decimal')
|
|
411
|
+
assert.equal(semantic.config?.defaultCurrency, 'USD')
|
|
412
|
+
assert.isFalse(semantic.config?.allowNegative)
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
test('should configure for international marketplace', ({ assert }) => {
|
|
416
|
+
const config: PriceConfig = {
|
|
417
|
+
storageFormat: 'complex_object',
|
|
418
|
+
allowedCurrencies: ['USD', 'EUR', 'GBP', 'JPY', 'CAD', 'AUD'],
|
|
419
|
+
decimalPlaces: 2,
|
|
420
|
+
allowNegative: false,
|
|
421
|
+
validateCurrencyCode: true,
|
|
422
|
+
}
|
|
423
|
+
const semantic = createPriceSemantic(config)
|
|
424
|
+
const errors = validatePriceConfig(config)
|
|
425
|
+
|
|
426
|
+
assert.lengthOf(errors, 0)
|
|
427
|
+
assert.equal(semantic.config?.storageFormat, 'complex_object')
|
|
428
|
+
assert.isArray(semantic.config?.allowedCurrencies)
|
|
429
|
+
assert.lengthOf(semantic.config?.allowedCurrencies || [], 6)
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
test('should configure for financial system with precision', ({ assert }) => {
|
|
433
|
+
const config: PriceConfig = {
|
|
434
|
+
storageFormat: 'integer_cents',
|
|
435
|
+
defaultCurrency: 'USD',
|
|
436
|
+
decimalPlaces: 4,
|
|
437
|
+
allowNegative: true,
|
|
438
|
+
validateCurrencyCode: true,
|
|
439
|
+
}
|
|
440
|
+
const semantic = createPriceSemantic(config)
|
|
441
|
+
const errors = validatePriceConfig(config)
|
|
442
|
+
|
|
443
|
+
assert.lengthOf(errors, 0)
|
|
444
|
+
assert.equal(semantic.config?.storageFormat, 'integer_cents')
|
|
445
|
+
assert.equal(semantic.config?.decimalPlaces, 4)
|
|
446
|
+
assert.isTrue(semantic.config?.allowNegative)
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
test('should configure for cryptocurrency trading', ({ assert }) => {
|
|
450
|
+
const config: PriceConfig = {
|
|
451
|
+
storageFormat: 'decimal',
|
|
452
|
+
defaultCurrency: 'BTC',
|
|
453
|
+
decimalPlaces: 8,
|
|
454
|
+
allowNegative: false,
|
|
455
|
+
validateCurrencyCode: false, // Custom crypto currency
|
|
456
|
+
}
|
|
457
|
+
const semantic = createPriceSemantic(config)
|
|
458
|
+
const errors = validatePriceConfig(config)
|
|
459
|
+
|
|
460
|
+
assert.lengthOf(errors, 0)
|
|
461
|
+
assert.equal(semantic.config?.defaultCurrency, 'BTC')
|
|
462
|
+
assert.equal(semantic.config?.decimalPlaces, 8)
|
|
463
|
+
assert.isFalse(semantic.config?.validateCurrencyCode)
|
|
464
|
+
})
|
|
465
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
createPublicUniqueNameSemantic,
|
|
4
|
+
isPublicUniqueNameSemantic,
|
|
5
|
+
DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG,
|
|
6
|
+
type PublicUniqueNameConfig,
|
|
7
|
+
} from '../../../../src/modeling/definitions/PublicUniqueName.js'
|
|
8
|
+
import { SemanticType, type AppliedDataSemantic } from '../../../../src/modeling/Semantics.js'
|
|
9
|
+
|
|
10
|
+
test.group('PublicUniqueName Semantic Configuration', () => {
|
|
11
|
+
test('should create semantic with default config', ({ assert }) => {
|
|
12
|
+
const semantic = createPublicUniqueNameSemantic()
|
|
13
|
+
assert.equal(semantic.id, SemanticType.PublicUniqueName)
|
|
14
|
+
assert.deepEqual(semantic.config, DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should create semantic with custom config', ({ assert }) => {
|
|
18
|
+
const config: PublicUniqueNameConfig = {
|
|
19
|
+
sourceField: 'title',
|
|
20
|
+
separator: '-',
|
|
21
|
+
maxLength: 50,
|
|
22
|
+
}
|
|
23
|
+
const semantic = createPublicUniqueNameSemantic(config)
|
|
24
|
+
assert.equal(semantic.id, SemanticType.PublicUniqueName)
|
|
25
|
+
assert.deepEqual(semantic.config, { ...DEFAULT_PUBLIC_UNIQUE_NAME_CONFIG, ...config })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should identify public unique name semantic', ({ assert }) => {
|
|
29
|
+
const semantic = createPublicUniqueNameSemantic()
|
|
30
|
+
assert.isTrue(isPublicUniqueNameSemantic(semantic))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should not identify non-public-unique-name semantic', ({ assert }) => {
|
|
34
|
+
// Simulate a different semantic
|
|
35
|
+
const fakeSemantic: AppliedDataSemantic = { id: SemanticType.Email, config: {} }
|
|
36
|
+
assert.isFalse(isPublicUniqueNameSemantic(fakeSemantic))
|
|
37
|
+
})
|
|
38
|
+
})
|