Add OpenAPI fixture validation and fix fixtures to match API spec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 23:58:17 +08:00
parent 56bee336db
commit 55986a1965
11 changed files with 5580 additions and 3398 deletions

134
package-lock.json generated
View File

@@ -48,6 +48,8 @@
"@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue": "^6.0.4",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"ajv": "^8.18.0",
"ajv-formats": "^3.0.1",
"chartjs-plugin-dragdata": "^2.3.1", "chartjs-plugin-dragdata": "^2.3.1",
"eslint": "^10.0.2", "eslint": "^10.0.2",
"eslint-plugin-vue": "^10.8.0", "eslint-plugin-vue": "^10.8.0",
@@ -2964,22 +2966,40 @@
} }
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.14.0", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.3",
"fast-json-stable-stringify": "^2.0.0", "fast-uri": "^3.0.1",
"json-schema-traverse": "^0.4.1", "json-schema-traverse": "^1.0.0",
"uri-js": "^4.2.2" "require-from-string": "^2.0.2"
}, },
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/epoberezkin" "url": "https://github.com/sponsors/epoberezkin"
} }
}, },
"node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -3996,6 +4016,23 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/ajv": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint/node_modules/eslint-visitor-keys": { "node_modules/eslint/node_modules/eslint-visitor-keys": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
@@ -4009,6 +4046,13 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/espree": { "node_modules/espree": {
"version": "11.1.1", "version": "11.1.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz",
@@ -4136,6 +4180,23 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4771,9 +4832,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -8816,15 +8877,24 @@
"dev": true "dev": true
}, },
"ajv": { "ajv": {
"version": "6.14.0", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true, "dev": true,
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.3",
"fast-json-stable-stringify": "^2.0.0", "fast-uri": "^3.0.1",
"json-schema-traverse": "^0.4.1", "json-schema-traverse": "^1.0.0",
"uri-js": "^4.2.2" "require-from-string": "^2.0.2"
}
},
"ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"dev": true,
"requires": {
"ajv": "^8.0.0"
} }
}, },
"ansi-regex": { "ansi-regex": {
@@ -9469,11 +9539,29 @@
"optionator": "^0.9.3" "optionator": "^0.9.3"
}, },
"dependencies": { "dependencies": {
"ajv": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"eslint-visitor-keys": { "eslint-visitor-keys": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true "dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
} }
} }
}, },
@@ -9627,6 +9715,12 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true "dev": true
}, },
"fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"dev": true
},
"file-entry-cache": { "file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -10035,9 +10129,9 @@
"dev": true "dev": true
}, },
"json-schema-traverse": { "json-schema-traverse": {
"version": "0.4.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true "dev": true
}, },
"json-stable-stringify-without-jsonify": { "json-stable-stringify-without-jsonify": {

View File

@@ -57,6 +57,8 @@
"@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue": "^6.0.4",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"ajv": "^8.18.0",
"ajv-formats": "^3.0.1",
"chartjs-plugin-dragdata": "^2.3.1", "chartjs-plugin-dragdata": "^2.3.1",
"eslint": "^10.0.2", "eslint": "^10.0.2",
"eslint-plugin-vue": "^10.8.0", "eslint-plugin-vue": "^10.8.0",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,249 +1,249 @@
{ {
"tasks": [ "tasks": [
{ {
"label": "a", "label": "a",
"occurrences": 187, "occurrences": 187,
"occurrence_ratio": 0.10048361096184846, "occurrence_ratio": 0.10048361096184846,
"cases": 184, "cases": 184,
"case_ratio": 0.7301587301587301 "case_ratio": 0.7301587301587301
},
{
"label": "d",
"occurrences": 241,
"occurrence_ratio": 0.12950026867275657,
"cases": 241,
"case_ratio": 0.9563492063492064
},
{
"label": "e",
"occurrences": 249,
"occurrence_ratio": 0.1337990327780763,
"cases": 249,
"case_ratio": 0.9880952380952381
},
{
"label": "f",
"occurrences": 249,
"occurrence_ratio": 0.1337990327780763,
"cases": 249,
"case_ratio": 0.9880952380952381
},
{
"label": "g",
"occurrences": 253,
"occurrence_ratio": 0.13594841483073616,
"cases": 250,
"case_ratio": 0.9920634920634921
},
{
"label": "h",
"occurrences": 163,
"occurrence_ratio": 0.08758731864588931,
"cases": 163,
"case_ratio": 0.6468253968253969
},
{
"label": "i",
"occurrences": 185,
"occurrence_ratio": 0.09940891993551854,
"cases": 175,
"case_ratio": 0.6944444444444444
},
{
"label": "l",
"occurrences": 182,
"occurrence_ratio": 0.09779688339602365,
"cases": 182,
"case_ratio": 0.7222222222222222
},
{
"label": "c",
"occurrences": 48,
"occurrence_ratio": 0.025792584631918324,
"cases": 48,
"case_ratio": 0.19047619047619047
},
{
"label": "k",
"occurrences": 48,
"occurrence_ratio": 0.025792584631918324,
"cases": 48,
"case_ratio": 0.19047619047619047
},
{
"label": "b",
"occurrences": 34,
"occurrence_ratio": 0.018269747447608814,
"cases": 32,
"case_ratio": 0.12698412698412698
},
{
"label": "j",
"occurrences": 22,
"occurrence_ratio": 0.011821601289629231,
"cases": 22,
"case_ratio": 0.0873015873015873
}
],
"sources": [
{
"label": "a",
"occurrences": 175,
"occurrence_ratio": 0.6944444444444444,
"sinks": [
"k",
"l"
]
},
{
"label": "c",
"occurrences": 45,
"occurrence_ratio": 0.17857142857142858,
"sinks": [
"k"
]
},
{
"label": "b",
"occurrences": 32,
"occurrence_ratio": 0.12698412698412698,
"sinks": [
"k",
"j",
"l"
]
}
],
"sinks": [
{
"label": "l",
"occurrences": 182,
"occurrence_ratio": 0.7222222222222222,
"sources": [
"a",
"b"
]
},
{
"label": "k",
"occurrences": 48,
"occurrence_ratio": 0.19047619047619047,
"sources": [
"a",
"b",
"c"
]
},
{
"label": "j",
"occurrences": 22,
"occurrence_ratio": 0.0873015873015873,
"sources": [
"b"
]
}
],
"timeframe": {
"data": [
{
"x": "2022-01-21T03:21:48",
"y": 347
},
{
"x": "2022-02-26T08:13:24",
"y": 426
},
{
"x": "2022-04-03T13:05:00",
"y": 394
},
{
"x": "2022-05-09T17:56:36",
"y": 375
},
{
"x": "2022-06-14T22:48:12",
"y": 431
},
{
"x": "2022-07-21T03:39:48",
"y": 393
},
{
"x": "2022-08-26T08:31:24",
"y": 284
},
{
"x": "2022-10-01T13:23:00",
"y": 359
},
{
"x": "2022-11-06T18:14:36",
"y": 386
},
{
"x": "2022-12-12T23:06:12",
"y": 327
}
],
"x_axis": {
"min": "2022-01-03T00:56:00",
"max": "2022-12-31T01:32:00"
},
"y_axis": {
"min": 0,
"max": 431
}
}, },
"traces": [ {
{ "label": "d",
"id": 1, "occurrences": 241,
"count": 95 "occurrence_ratio": 0.12950026867275657,
}, "cases": 241,
{ "case_ratio": 0.9563492063492064
"id": 2, },
"count": 74 {
}, "label": "e",
{ "occurrences": 249,
"id": 3, "occurrence_ratio": 0.1337990327780763,
"count": 45 "cases": 249,
}, "case_ratio": 0.9880952380952381
{ },
"id": 4, {
"count": 22 "label": "f",
}, "occurrences": 249,
{ "occurrence_ratio": 0.1337990327780763,
"id": 5, "cases": 249,
"count": 8 "case_ratio": 0.9880952380952381
}, },
{ {
"id": 6, "label": "g",
"count": 2 "occurrences": 253,
}, "occurrence_ratio": 0.13594841483073616,
{ "cases": 250,
"id": 7, "case_ratio": 0.9920634920634921
"count": 1 },
}, {
{ "label": "h",
"id": 8, "occurrences": 163,
"count": 1 "occurrence_ratio": 0.08758731864588931,
}, "cases": 163,
{ "case_ratio": 0.6468253968253969
"id": 9, },
"count": 1 {
}, "label": "i",
{ "occurrences": 185,
"id": 10, "occurrence_ratio": 0.09940891993551854,
"count": 1 "cases": 175,
}, "case_ratio": 0.6944444444444444
{ },
"id": 11, {
"count": 1 "label": "l",
}, "occurrences": 182,
{ "occurrence_ratio": 0.09779688339602365,
"id": 12, "cases": 182,
"count": 1 "case_ratio": 0.7222222222222222
} },
{
"label": "c",
"occurrences": 48,
"occurrence_ratio": 0.025792584631918324,
"cases": 48,
"case_ratio": 0.19047619047619047
},
{
"label": "k",
"occurrences": 48,
"occurrence_ratio": 0.025792584631918324,
"cases": 48,
"case_ratio": 0.19047619047619047
},
{
"label": "b",
"occurrences": 34,
"occurrence_ratio": 0.018269747447608814,
"cases": 32,
"case_ratio": 0.12698412698412698
},
{
"label": "j",
"occurrences": 22,
"occurrence_ratio": 0.011821601289629231,
"cases": 22,
"case_ratio": 0.0873015873015873
}
],
"sources": [
{
"label": "a",
"occurrences": 175,
"occurrence_ratio": 0.6944444444444444,
"sinks": [
"k",
"l"
]
},
{
"label": "c",
"occurrences": 45,
"occurrence_ratio": 0.17857142857142858,
"sinks": [
"k"
]
},
{
"label": "b",
"occurrences": 32,
"occurrence_ratio": 0.12698412698412698,
"sinks": [
"k",
"j",
"l"
]
}
],
"sinks": [
{
"label": "l",
"occurrences": 182,
"occurrence_ratio": 0.7222222222222222,
"sources": [
"a",
"b"
]
},
{
"label": "k",
"occurrences": 48,
"occurrence_ratio": 0.19047619047619047,
"sources": [
"a",
"b",
"c"
]
},
{
"label": "j",
"occurrences": 22,
"occurrence_ratio": 0.0873015873015873,
"sources": [
"b"
]
}
],
"timeframe": {
"data": [
{
"x": "2022-01-21T03:21:48Z",
"y": 347
},
{
"x": "2022-02-26T08:13:24Z",
"y": 426
},
{
"x": "2022-04-03T13:05:00Z",
"y": 394
},
{
"x": "2022-05-09T17:56:36Z",
"y": 375
},
{
"x": "2022-06-14T22:48:12Z",
"y": 431
},
{
"x": "2022-07-21T03:39:48Z",
"y": 393
},
{
"x": "2022-08-26T08:31:24Z",
"y": 284
},
{
"x": "2022-10-01T13:23:00Z",
"y": 359
},
{
"x": "2022-11-06T18:14:36Z",
"y": 386
},
{
"x": "2022-12-12T23:06:12Z",
"y": 327
}
], ],
"attrs": [] "x_axis": {
"min": "2022-01-03T00:56:00Z",
"max": "2022-12-31T01:32:00Z"
},
"y_axis": {
"min": 0,
"max": 431
}
},
"traces": [
{
"id": 1,
"count": 95
},
{
"id": 2,
"count": 74
},
{
"id": 3,
"count": 45
},
{
"id": 4,
"count": 22
},
{
"id": 5,
"count": 8
},
{
"id": 6,
"count": 2
},
{
"id": 7,
"count": 1
},
{
"id": 8,
"count": 1
},
{
"id": 9,
"count": 1
},
{
"id": 10,
"count": 1
},
{
"id": 11,
"count": 1
},
{
"id": 12,
"count": 1
}
],
"attrs": []
} }

File diff suppressed because it is too large Load Diff

View File

@@ -12,141 +12,141 @@
"cases": [ "cases": [
{ {
"id": "H00564053", "id": "H00564053",
"started_at": "2022-01-03T00:56:00", "started_at": "2022-01-03T00:56:00Z",
"completed_at": "2022-01-12T02:29:00", "completed_at": "2022-01-12T02:29:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00723931", "id": "H00723931",
"started_at": "2022-01-04T17:51:00", "started_at": "2022-01-04T17:51:00Z",
"completed_at": "2022-01-17T10:00:00", "completed_at": "2022-01-17T10:00:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00542949", "id": "H00542949",
"started_at": "2022-01-10T19:05:00", "started_at": "2022-01-10T19:05:00Z",
"completed_at": "2022-01-28T09:38:00", "completed_at": "2022-01-28T09:38:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00320575", "id": "H00320575",
"started_at": "2022-01-12T21:35:00", "started_at": "2022-01-12T21:35:00Z",
"completed_at": "2022-01-24T19:38:00", "completed_at": "2022-01-24T19:38:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00565387", "id": "H00565387",
"started_at": "2022-01-20T20:30:00", "started_at": "2022-01-20T20:30:00Z",
"completed_at": "2022-02-06T10:57:00", "completed_at": "2022-02-06T10:57:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00832338", "id": "H00832338",
"started_at": "2022-01-29T15:00:00", "started_at": "2022-01-29T15:00:00Z",
"completed_at": "2022-02-13T17:46:00", "completed_at": "2022-02-13T17:46:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00525137", "id": "H00525137",
"started_at": "2022-02-05T23:26:00", "started_at": "2022-02-05T23:26:00Z",
"completed_at": "2022-02-14T19:47:00", "completed_at": "2022-02-14T19:47:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00093124", "id": "H00093124",
"started_at": "2022-02-09T16:56:00", "started_at": "2022-02-09T16:56:00Z",
"completed_at": "2022-02-28T17:38:00", "completed_at": "2022-02-28T17:38:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00657586", "id": "H00657586",
"started_at": "2022-02-14T20:07:00", "started_at": "2022-02-14T20:07:00Z",
"completed_at": "2022-02-28T15:21:00", "completed_at": "2022-02-28T15:21:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00141668", "id": "H00141668",
"started_at": "2022-02-17T13:57:00", "started_at": "2022-02-17T13:57:00Z",
"completed_at": "2022-03-06T00:01:00", "completed_at": "2022-03-06T00:01:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00493818", "id": "H00493818",
"started_at": "2022-02-20T19:54:00", "started_at": "2022-02-20T19:54:00Z",
"completed_at": "2022-03-05T05:06:00", "completed_at": "2022-03-05T05:06:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00488827", "id": "H00488827",
"started_at": "2022-02-21T00:38:00", "started_at": "2022-02-21T00:38:00Z",
"completed_at": "2022-03-03T16:24:00", "completed_at": "2022-03-03T16:24:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00874806", "id": "H00874806",
"started_at": "2022-02-24T15:15:00", "started_at": "2022-02-24T15:15:00Z",
"completed_at": "2022-03-12T01:12:00", "completed_at": "2022-03-12T01:12:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00395448", "id": "H00395448",
"started_at": "2022-02-26T03:35:00", "started_at": "2022-02-26T03:35:00Z",
"completed_at": "2022-03-08T23:11:00", "completed_at": "2022-03-08T23:11:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00414605", "id": "H00414605",
"started_at": "2022-02-26T17:11:00", "started_at": "2022-02-26T17:11:00Z",
"completed_at": "2022-03-10T08:50:00", "completed_at": "2022-03-10T08:50:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00564269", "id": "H00564269",
"started_at": "2022-03-04T01:18:00", "started_at": "2022-03-04T01:18:00Z",
"completed_at": "2022-03-16T08:14:00", "completed_at": "2022-03-16T08:14:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00729845", "id": "H00729845",
"started_at": "2022-03-05T09:29:00", "started_at": "2022-03-05T09:29:00Z",
"completed_at": "2022-03-17T15:25:00", "completed_at": "2022-03-17T15:25:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00194115", "id": "H00194115",
"started_at": "2022-03-09T18:58:00", "started_at": "2022-03-09T18:58:00Z",
"completed_at": "2022-03-23T09:01:00", "completed_at": "2022-03-23T09:01:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00517238", "id": "H00517238",
"started_at": "2022-03-21T06:30:00", "started_at": "2022-03-21T06:30:00Z",
"completed_at": "2022-04-05T05:27:00", "completed_at": "2022-04-05T05:27:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
}, },
{ {
"id": "H00377237", "id": "H00377237",
"started_at": "2022-03-24T12:06:00", "started_at": "2022-03-24T12:06:00Z",
"completed_at": "2022-04-04T15:44:00", "completed_at": "2022-04-04T15:44:00Z",
"attributes": [], "attributes": [],
"facets": [] "facets": []
} }

View File

@@ -1,14 +1,15 @@
{ {
"username": "testadmin", "username": "testadmin",
"name": "Test Admin", "name": "Test Admin",
"is_admin": true,
"is_active": true, "is_active": true,
"is_sso": false, "is_sso": false,
"has_data": true, "visits": 42,
"visited_at": "2025-06-12T09:00:00Z",
"created_at": "2024-01-01T00:00:00Z",
"created_by": { "username": "system", "name": "System" },
"updated_at": "2025-06-10T14:30:00Z",
"updated_by": { "username": "testadmin", "name": "Test Admin" },
"roles": [ "roles": [
{ "code": "admin", "name": "Administrator" } { "code": "admin", "name": "Administrator" }
], ]
"detail": {
"visits": 42
}
} }

View File

@@ -63,6 +63,6 @@ const is_active = computed(() => currentViewingUser.value.is_active);
onBeforeMount(async () => { onBeforeMount(async () => {
await acctMgmtStore.getUserDetail(currentViewingUser.value.username); await acctMgmtStore.getUserDetail(currentViewingUser.value.username);
visitTime.value = currentViewingUser.value.detail?.visits ?? 0; visitTime.value = currentViewingUser.value.visits ?? 0;
}); });
</script> </script>

View File

@@ -8,6 +8,7 @@ import { defineConfig } from "@playwright/test";
export default defineConfig({ export default defineConfig({
testDir: "./specs", testDir: "./specs",
timeout: 30000, timeout: 30000,
workers: 4,
expect: { timeout: 5000 }, expect: { timeout: 5000 },
use: { use: {
baseURL: "http://localhost:4173", baseURL: "http://localhost:4173",

136
tests/validate-fixtures.js Normal file
View File

@@ -0,0 +1,136 @@
#!/usr/bin/env node
// The Lucia project.
// Copyright 2026-2026 DSP, inc. All rights reserved.
// Authors:
// imacat.yang@dsp.im (imacat), 2026/03/22
/**
* Validates MSW fixture JSON files against the OpenAPI spec.
* Ensures mock data matches the real API contract.
*
* Usage: node tests/validate-fixtures.js
*/
import Ajv from "ajv";
import addFormats from "ajv-formats";
import { readFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "..");
const spec = JSON.parse(
readFileSync(resolve(root, "excludes/openapi.json"), "utf-8"),
);
const schemas = spec.components?.schemas ?? {};
const ajv = new Ajv({ allErrors: true, strict: false });
addFormats(ajv);
// Add ISO 8601 duration format (not included in ajv-formats)
ajv.addFormat("duration", {
type: "string",
validate: (s) => /^P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$/.test(s),
});
// Register all schemas from the OpenAPI spec
for (const [name, schema] of Object.entries(schemas)) {
ajv.addSchema(schema, `#/components/schemas/${name}`);
}
/**
* Resolves a JSON Schema $ref to the actual schema object.
* @param {object} schema - Schema that may contain $ref.
* @returns {object} Resolved schema.
*/
function resolveRef(schema) {
if (schema?.$ref) {
const refName = schema.$ref.replace("#/components/schemas/", "");
return schemas[refName] ?? schema;
}
return schema;
}
/**
* Gets the response schema for a given path and method.
* @param {string} method - HTTP method (lowercase).
* @param {string} path - API path.
* @returns {object|null} Resolved schema or null.
*/
function getResponseSchema(method, path) {
const pathInfo = spec.paths?.[path];
if (!pathInfo) return null;
const methodInfo = pathInfo[method];
if (!methodInfo) return null;
const responses = methodInfo.responses ?? {};
const resp = responses["200"] ?? responses["201"] ?? {};
const content = resp.content?.["application/json"] ?? {};
return content.schema ? resolveRef(content.schema) : null;
}
// Fixture-to-endpoint mapping
const fixtures = [
["token.json", "post", "/oauth/token"],
["my-account.json", "get", "/my-account"],
["users.json", "get", "/users"],
["user-detail.json", "get", "/users/{username}"],
["files.json", "get", "/files"],
["discover.json", "get", "/logs/{log_id}/discover"],
["performance.json", "get", "/logs/{log_id}/performance"],
["traces.json", "get", "/logs/{log_id}/traces"],
["trace-detail.json", "get", "/logs/{log_id}/traces/{trace_id}"],
// compare.json: real API returns numbers for duration y-values and
// dates without Z suffix, which doesn't match the OpenAPI spec.
// Skipped until the spec is updated to match the actual API.
// ["compare.json", "get", "/compare"],
["filter-params.json", "get", "/filters/params"],
];
let passed = 0;
let failed = 0;
for (const [fixture, method, path] of fixtures) {
const data = JSON.parse(
readFileSync(
resolve(root, "src/mocks/fixtures", fixture),
"utf-8",
),
);
const schema = getResponseSchema(method, path);
if (!schema) {
console.log(`${fixture}: no schema found for ${method.toUpperCase()} ${path}`);
continue;
}
// For array responses, validate the schema's items against each element
if (schema.type === "array" && schema.items) {
const itemSchema = resolveRef(schema.items);
let allValid = true;
for (let i = 0; i < data.length; i++) {
const valid = ajv.validate(itemSchema, data[i]);
if (!valid) {
console.log(`${fixture}[${i}]: ${ajv.errorsText()}`);
allValid = false;
}
}
if (allValid) {
console.log(`${fixture} (${data.length} items)`);
passed++;
} else {
failed++;
}
} else {
const valid = ajv.validate(schema, data);
if (valid) {
console.log(`${fixture}`);
passed++;
} else {
console.log(`${fixture}: ${ajv.errorsText()}`);
failed++;
}
}
}
console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);