@api-client/core 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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,73 @@
|
|
|
1
|
+
import type { AppliedDataSemantic } from '../Semantics.js'
|
|
2
|
+
import { SemanticType } from '../Semantics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for the Summary semantic.
|
|
6
|
+
* Summary is a simple semantic without configuration options.
|
|
7
|
+
*/
|
|
8
|
+
export interface SummaryConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Custom metadata for the summary field.
|
|
11
|
+
*/
|
|
12
|
+
metadata?: Record<string, unknown>
|
|
13
|
+
/**
|
|
14
|
+
* Index signature to allow additional properties.
|
|
15
|
+
*/
|
|
16
|
+
[key: string]: unknown
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default configuration for the Summary semantic.
|
|
21
|
+
*/
|
|
22
|
+
export const DEFAULT_SUMMARY_CONFIG: SummaryConfig = {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Summary semantic application.
|
|
26
|
+
* @param config - Optional configuration (not used for simple semantic)
|
|
27
|
+
* @returns AppliedDataSemantic for Summary
|
|
28
|
+
*/
|
|
29
|
+
export const createSummarySemantic = (config?: SummaryConfig): AppliedDataSemantic => ({
|
|
30
|
+
id: SemanticType.Summary,
|
|
31
|
+
config: config ? { ...DEFAULT_SUMMARY_CONFIG, ...config } : { ...DEFAULT_SUMMARY_CONFIG },
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Type guard to check if a semantic is a Summary semantic.
|
|
36
|
+
* @param semantic - The semantic to check
|
|
37
|
+
* @returns True if the semantic is a Summary semantic
|
|
38
|
+
*/
|
|
39
|
+
export const isSummarySemantic = (
|
|
40
|
+
semantic: AppliedDataSemantic
|
|
41
|
+
): semantic is AppliedDataSemantic & { id: SemanticType.Summary } => semantic.id === SemanticType.Summary
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extracts the configuration from a Summary semantic application.
|
|
45
|
+
* @param semantic - The semantic application
|
|
46
|
+
* @returns The Summary configuration or undefined if not a Summary semantic
|
|
47
|
+
*/
|
|
48
|
+
export const getSummaryConfig = (semantic: AppliedDataSemantic): SummaryConfig | undefined => {
|
|
49
|
+
if (!isSummarySemantic(semantic)) {
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
return semantic.config as SummaryConfig | undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validates Summary semantic configuration.
|
|
57
|
+
* @returns True if the configuration is valid
|
|
58
|
+
*/
|
|
59
|
+
export const validateSummaryConfig = (): boolean => {
|
|
60
|
+
// No validation needed for simple semantic
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merges Summary configurations, with the second config taking precedence.
|
|
66
|
+
* @param base - The base configuration
|
|
67
|
+
* @param override - The configuration to merge on top
|
|
68
|
+
* @returns Merged configuration
|
|
69
|
+
*/
|
|
70
|
+
export const mergeSummaryConfig = (base: SummaryConfig, override: SummaryConfig): SummaryConfig => {
|
|
71
|
+
// No merging needed for simple semantic
|
|
72
|
+
return { ...base, ...override }
|
|
73
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { AppliedDataSemantic } from '../Semantics.js'
|
|
2
|
+
import { SemanticType } from '../Semantics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for the Tags semantic.
|
|
6
|
+
* Controls allowed tags, validation, and formatting for associations.
|
|
7
|
+
*/
|
|
8
|
+
export interface TagsConfig {
|
|
9
|
+
/**
|
|
10
|
+
* List of allowed tags.
|
|
11
|
+
*/
|
|
12
|
+
allowedTags?: string[]
|
|
13
|
+
/**
|
|
14
|
+
* Whether to allow custom tags not in the allowedTags list.
|
|
15
|
+
*/
|
|
16
|
+
allowCustomTags?: boolean
|
|
17
|
+
/**
|
|
18
|
+
* Whether tags are case sensitive.
|
|
19
|
+
*/
|
|
20
|
+
caseSensitive?: boolean
|
|
21
|
+
/**
|
|
22
|
+
* Whether to allow Unicode characters in tags.
|
|
23
|
+
*/
|
|
24
|
+
allowUnicode?: boolean
|
|
25
|
+
/**
|
|
26
|
+
* Custom metadata for the tags association.
|
|
27
|
+
*/
|
|
28
|
+
metadata?: Record<string, unknown>
|
|
29
|
+
/**
|
|
30
|
+
* Index signature to allow additional properties.
|
|
31
|
+
*/
|
|
32
|
+
[key: string]: unknown
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Type-safe configuration for Tags semantic.
|
|
37
|
+
*/
|
|
38
|
+
export interface AppliedTagsSemantic extends AppliedDataSemantic {
|
|
39
|
+
id: SemanticType.Tags
|
|
40
|
+
config?: TagsConfig
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Type guard to check if a semantic is a Tags semantic.
|
|
45
|
+
*/
|
|
46
|
+
export const isTagsSemantic = (semantic: AppliedDataSemantic): semantic is AppliedTagsSemantic => {
|
|
47
|
+
return semantic.id === SemanticType.Tags
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Helper function to create a Tags semantic with configuration.
|
|
52
|
+
*/
|
|
53
|
+
export const createTagsSemantic = (config: TagsConfig = {}): AppliedTagsSemantic => {
|
|
54
|
+
const mergedConfig = {
|
|
55
|
+
...DEFAULT_TAGS_CONFIG,
|
|
56
|
+
...config,
|
|
57
|
+
}
|
|
58
|
+
if (config.metadata) {
|
|
59
|
+
mergedConfig.metadata = { ...config.metadata }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
id: SemanticType.Tags,
|
|
64
|
+
config: mergedConfig,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Default configuration for Tags semantic.
|
|
70
|
+
*/
|
|
71
|
+
export const DEFAULT_TAGS_CONFIG: TagsConfig = {
|
|
72
|
+
allowCustomTags: true,
|
|
73
|
+
caseSensitive: false,
|
|
74
|
+
allowUnicode: false,
|
|
75
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { AppliedDataSemantic } from '../Semantics.js'
|
|
2
|
+
import { SemanticType } from '../Semantics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for the URL semantic.
|
|
6
|
+
* Controls validation, allowed protocols, and formatting.
|
|
7
|
+
*/
|
|
8
|
+
export interface URLConfig {
|
|
9
|
+
/**
|
|
10
|
+
* List of allowed protocols (e.g., ['http', 'https']).
|
|
11
|
+
*/
|
|
12
|
+
allowedProtocols?: string[]
|
|
13
|
+
/**
|
|
14
|
+
* Whether to require HTTPS protocol (default: false).
|
|
15
|
+
*/
|
|
16
|
+
requireHttps?: boolean
|
|
17
|
+
/**
|
|
18
|
+
* Whether to allow query parameters in the URL.
|
|
19
|
+
*/
|
|
20
|
+
allowQueryParams?: boolean
|
|
21
|
+
/**
|
|
22
|
+
* Whether to allow URL fragments (e.g., #section).
|
|
23
|
+
*/
|
|
24
|
+
allowFragments?: boolean
|
|
25
|
+
/**
|
|
26
|
+
* Whether to allow internationalized domain names.
|
|
27
|
+
*/
|
|
28
|
+
allowInternational?: boolean
|
|
29
|
+
/**
|
|
30
|
+
* Whether to allow IP addresses as hostnames.
|
|
31
|
+
*/
|
|
32
|
+
allowIP?: boolean
|
|
33
|
+
/**
|
|
34
|
+
* Whether to allow port numbers in the URL.
|
|
35
|
+
*/
|
|
36
|
+
allowPort?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* Whether to allow authentication info (user:pass@host).
|
|
39
|
+
*/
|
|
40
|
+
allowAuthentication?: boolean
|
|
41
|
+
/**
|
|
42
|
+
* Custom metadata for the URL field.
|
|
43
|
+
*/
|
|
44
|
+
metadata?: Record<string, unknown>
|
|
45
|
+
/**
|
|
46
|
+
* Index signature to allow additional properties.
|
|
47
|
+
*/
|
|
48
|
+
[key: string]: unknown
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Type-safe configuration for URL semantic.
|
|
53
|
+
*/
|
|
54
|
+
export interface AppliedURLSemantic extends AppliedDataSemantic {
|
|
55
|
+
id: SemanticType.URL
|
|
56
|
+
config?: URLConfig
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Type guard to check if a semantic is a URL semantic.
|
|
61
|
+
*/
|
|
62
|
+
export const isURLSemantic = (semantic: AppliedDataSemantic): semantic is AppliedURLSemantic => {
|
|
63
|
+
return semantic.id === SemanticType.URL
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Helper function to create a URL semantic with configuration.
|
|
68
|
+
*/
|
|
69
|
+
export const createURLSemantic = (config: URLConfig = {}): AppliedURLSemantic => {
|
|
70
|
+
const mergedConfig = {
|
|
71
|
+
...DEFAULT_URL_CONFIG,
|
|
72
|
+
...config,
|
|
73
|
+
}
|
|
74
|
+
if (config.metadata) {
|
|
75
|
+
mergedConfig.metadata = { ...config.metadata }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: SemanticType.URL,
|
|
80
|
+
config: mergedConfig,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Default configuration for URL semantic.
|
|
86
|
+
*/
|
|
87
|
+
export const DEFAULT_URL_CONFIG: URLConfig = {
|
|
88
|
+
allowedProtocols: ['http', 'https'],
|
|
89
|
+
requireHttps: false,
|
|
90
|
+
allowQueryParams: true,
|
|
91
|
+
allowFragments: true,
|
|
92
|
+
allowInternational: true,
|
|
93
|
+
allowIP: true,
|
|
94
|
+
allowPort: true,
|
|
95
|
+
allowAuthentication: false,
|
|
96
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DomainEntityKind } from '../../models/kinds.js'
|
|
2
2
|
import type { DataDomain } from '../DataDomain.js'
|
|
3
|
-
import { SemanticType } from '../Semantics.js'
|
|
3
|
+
import { DataSemantics, isPropertySemantic, SemanticType } from '../Semantics.js'
|
|
4
4
|
import type { DomainValidation } from './rules.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -29,6 +29,10 @@ export class SemanticValidation {
|
|
|
29
29
|
const softDeleteSemantics = this.validateSoftDeleteSemantics()
|
|
30
30
|
results.push(...softDeleteSemantics)
|
|
31
31
|
|
|
32
|
+
// Validate property semantics data types
|
|
33
|
+
const propertySemanticsDataTypes = this.validatePropertySemanticsDataTypes()
|
|
34
|
+
results.push(...propertySemanticsDataTypes)
|
|
35
|
+
|
|
32
36
|
return results
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -142,4 +146,34 @@ export class SemanticValidation {
|
|
|
142
146
|
|
|
143
147
|
return results
|
|
144
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Validates if property semantics are applied to properties with compatible data types.
|
|
152
|
+
*/
|
|
153
|
+
private validatePropertySemanticsDataTypes(): DomainValidation[] {
|
|
154
|
+
const results: DomainValidation[] = []
|
|
155
|
+
|
|
156
|
+
for (const entity of this.domain.listEntities()) {
|
|
157
|
+
for (const property of entity.listProperties()) {
|
|
158
|
+
for (const appliedSemantic of property.semantics) {
|
|
159
|
+
const semanticDefinition = DataSemantics[appliedSemantic.id]
|
|
160
|
+
if (isPropertySemantic(semanticDefinition) && semanticDefinition.applicableDataTypes) {
|
|
161
|
+
if (!semanticDefinition.applicableDataTypes.includes(property.type)) {
|
|
162
|
+
results.push({
|
|
163
|
+
field: 'semantics',
|
|
164
|
+
rule: 'type_mismatch',
|
|
165
|
+
message: `The "${property.info.getLabel()}" property has the "${semanticDefinition.displayName}" semantic applied, but its type "${property.type}" is not compatible.`,
|
|
166
|
+
help: `The "${semanticDefinition.displayName}" semantic can only be applied to properties of type(s): ${semanticDefinition.applicableDataTypes.join(', ')}.`,
|
|
167
|
+
severity: 'error',
|
|
168
|
+
key: property.key,
|
|
169
|
+
kind: property.kind,
|
|
170
|
+
parent: entity.key,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return results
|
|
178
|
+
}
|
|
145
179
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example test setup and usage guide for API Client Core
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates how to use the testing infrastructure
|
|
5
|
+
* and provides working examples for both Node.js and browser tests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Node.js test example using Japa
|
|
9
|
+
import { test } from '@japa/runner'
|
|
10
|
+
import { TestUtils } from './test-utils.js'
|
|
11
|
+
|
|
12
|
+
// Example: Testing a utility function
|
|
13
|
+
test.group('Example Node.js Tests', (group) => {
|
|
14
|
+
group.each.setup(() => {
|
|
15
|
+
// Setup before each test
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
console.log('Setting up test...')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('should demonstrate basic testing', ({ assert }) => {
|
|
21
|
+
const result = 'hello world'
|
|
22
|
+
assert.equal(result, 'hello world')
|
|
23
|
+
assert.typeOf(result, 'string')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('should test async operations', async ({ assert }) => {
|
|
27
|
+
const data = TestUtils.generateTestData()
|
|
28
|
+
assert.typeOf(data.id, 'string')
|
|
29
|
+
assert.typeOf(data.timestamp, 'number')
|
|
30
|
+
assert.typeOf(data.randomBoolean, 'boolean')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should test HTTP mocking', async ({ assert }) => {
|
|
34
|
+
const mockResponse = TestUtils.createMockResponse(200, { success: true })
|
|
35
|
+
const cleanup = TestUtils.mockFetch(mockResponse)
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch('https://api.example.com/test')
|
|
39
|
+
const data = await response.json()
|
|
40
|
+
assert.isTrue(data.success)
|
|
41
|
+
assert.equal(response.status, 200)
|
|
42
|
+
} finally {
|
|
43
|
+
cleanup()
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('should test error handling', async ({ assert }) => {
|
|
48
|
+
const error = await TestUtils.assertThrows(() => {
|
|
49
|
+
throw new Error('Test error')
|
|
50
|
+
}, 'Test error')
|
|
51
|
+
assert.equal(error.message, 'Test error')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('should validate object properties', ({ assert }) => {
|
|
55
|
+
const testObj = { name: 'test', value: 42 }
|
|
56
|
+
|
|
57
|
+
// This should not throw
|
|
58
|
+
TestUtils.validateRequiredProperties(testObj, ['name', 'value'])
|
|
59
|
+
|
|
60
|
+
// This should throw
|
|
61
|
+
assert.throws(() => {
|
|
62
|
+
// @ts-expect-error: Intentionally missing property
|
|
63
|
+
TestUtils.validateRequiredProperties(testObj, ['name', 'missing'])
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('should create test URLs with parameters', ({ assert }) => {
|
|
68
|
+
const url = TestUtils.createTestUrl('/api/users', {
|
|
69
|
+
page: '1',
|
|
70
|
+
limit: '10',
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
assert.equal(url.pathname, '/api/users')
|
|
74
|
+
assert.equal(url.searchParams.get('page'), '1')
|
|
75
|
+
assert.equal(url.searchParams.get('limit'), '10')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('should create test requests', ({ assert }) => {
|
|
79
|
+
const request = TestUtils.createTestRequest(
|
|
80
|
+
'POST',
|
|
81
|
+
'https://api.example.com/users',
|
|
82
|
+
JSON.stringify({ name: 'John' }),
|
|
83
|
+
{ Authorization: 'Bearer token123' }
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
assert.equal(request.method, 'POST')
|
|
87
|
+
assert.equal(request.url, 'https://api.example.com/users')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('should create mock auth config', ({ assert }) => {
|
|
91
|
+
const config = TestUtils.createMockAuthConfig()
|
|
92
|
+
assert.typeOf(config.clientId, 'string')
|
|
93
|
+
assert.typeOf(config.clientSecret, 'string')
|
|
94
|
+
assert.isArray(config.scopes)
|
|
95
|
+
assert.include(config.scopes, 'read')
|
|
96
|
+
assert.include(config.scopes, 'write')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('should test with sinon mocks', ({ sinon, assert }) => {
|
|
100
|
+
const mockObj = {
|
|
101
|
+
getValue: () => 'original',
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const stub = sinon.stub(mockObj, 'getValue')
|
|
105
|
+
stub.returns('mocked')
|
|
106
|
+
|
|
107
|
+
assert.equal(mockObj.getValue(), 'mocked')
|
|
108
|
+
|
|
109
|
+
stub.restore()
|
|
110
|
+
assert.equal(mockObj.getValue(), 'original')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('should test timeout behavior', async ({ assert }) => {
|
|
114
|
+
const start = Date.now()
|
|
115
|
+
await TestUtils.sleep(100)
|
|
116
|
+
const elapsed = Date.now() - start
|
|
117
|
+
|
|
118
|
+
assert.isAtLeast(elapsed, 90) // Allow some variance
|
|
119
|
+
assert.isAtMost(elapsed, 150)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
export default {
|
|
124
|
+
setupNodeTests: () => {
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.log('Node.js test environment initialized')
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
setupBrowserTests: () => {
|
|
130
|
+
// eslint-disable-next-line no-console
|
|
131
|
+
console.log('Browser test environment initialized')
|
|
132
|
+
},
|
|
133
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template for Node.js tests using Japa
|
|
3
|
+
* Copy this file and rename it to create new test suites.
|
|
4
|
+
*/
|
|
5
|
+
import { test } from '@japa/runner'
|
|
6
|
+
|
|
7
|
+
test.group('YourModuleName', (group) => {
|
|
8
|
+
// Setup runs before each test in the group
|
|
9
|
+
group.each.setup(() => {
|
|
10
|
+
// Initialize test data, mocks, etc.
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
// Teardown runs after each test in the group
|
|
14
|
+
group.each.teardown(() => {
|
|
15
|
+
// Clean up resources, restore mocks, etc.
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('should describe what this test does', ({ assert }) => {
|
|
19
|
+
// Arrange
|
|
20
|
+
const input = 'test-input'
|
|
21
|
+
|
|
22
|
+
// Act
|
|
23
|
+
const result = input.toUpperCase()
|
|
24
|
+
|
|
25
|
+
// Assert
|
|
26
|
+
assert.equal(result, 'TEST-INPUT')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('should test async operations', async ({ assert }) => {
|
|
30
|
+
// Arrange
|
|
31
|
+
const promise = Promise.resolve('async-result')
|
|
32
|
+
|
|
33
|
+
// Act
|
|
34
|
+
const result = await promise
|
|
35
|
+
|
|
36
|
+
// Assert
|
|
37
|
+
assert.equal(result, 'async-result')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('should test error cases', async ({ assert }) => {
|
|
41
|
+
// Assert that function throws
|
|
42
|
+
await assert.rejects(() => Promise.reject(new Error('Expected error')), 'Expected error')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('should use sinon for mocking', ({ sinon, assert }) => {
|
|
46
|
+
// Arrange
|
|
47
|
+
const mockObject = {
|
|
48
|
+
method: () => 'original',
|
|
49
|
+
}
|
|
50
|
+
const stub = sinon.stub(mockObject, 'method')
|
|
51
|
+
stub.returns('mocked')
|
|
52
|
+
|
|
53
|
+
// Act
|
|
54
|
+
const result = mockObject.method()
|
|
55
|
+
|
|
56
|
+
// Assert
|
|
57
|
+
assert.equal(result, 'mocked')
|
|
58
|
+
|
|
59
|
+
// Cleanup (optional - sinon auto-restores)
|
|
60
|
+
stub.restore()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('should test with custom timeout', async ({ assert }) => {
|
|
64
|
+
// This test has a longer timeout
|
|
65
|
+
const start = Date.now()
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
67
|
+
const elapsed = Date.now() - start
|
|
68
|
+
assert.isAtLeast(elapsed, 90)
|
|
69
|
+
}).timeout(5000) // 5 second timeout
|
|
70
|
+
|
|
71
|
+
test('should be skipped', ({ assert }) => {
|
|
72
|
+
// This test is skipped
|
|
73
|
+
assert.fail('This should not run')
|
|
74
|
+
}).skip(true)
|
|
75
|
+
})
|