Skip to content

Commit d52ca96

Browse files
committed
test(runValidation): Add tests
1 parent e4d8966 commit d52ca96

File tree

6 files changed

+394
-30
lines changed

6 files changed

+394
-30
lines changed

resources/integration_cards_guidelines.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
## 2. Validation
4343
- **ALWAYS** ensure that `manifest.json` file is valid JSON.
4444
- **ALWAYS** ensure that in `manifest.json` file the property `sap.app/type` is set to `"card"`.
45+
- **ALWAYS** validate the `manifest.json` against the UI5 Manifest schema. You must do it using the `run_manifest_validation` tool.
4546
- **ALWAYS** avoid deprecated properties in `manifest.json` and other places.
4647
- **NEVER** treat Integration Cards project as UI5 project, except for cards of type "Component".
4748

src/tools/run_manifest_validation/runValidation.ts

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import {readFile} from "fs/promises";
55
import {getLogger} from "@ui5/logger";
66
import {InvalidInputError} from "../../utils.js";
77
import {getManifestSchema} from "../../utils/ui5Manifest.js";
8+
import {Mutex} from "async-mutex";
89

910
const log = getLogger("tools:run_manifest_validation:runValidation");
10-
const schemaCache = new Map<string, Promise<object>>();
11+
const schemaCache = new Map<string, AnySchemaObject>();
12+
const fetchSchemaMutex = new Mutex();
1113

12-
// Configuration constants
1314
const AJV_SCHEMA_PATHS = {
1415
draft06: "node_modules/ajv/dist/refs/json-schema-draft-06.json",
1516
draft07: "node_modules/ajv/dist/refs/json-schema-draft-07.json",
@@ -22,46 +23,35 @@ async function createUI5ManifestValidateFunction(ui5Schema: object) {
2223
strict: false, // Allow additional properties that are not in schema
2324
unicodeRegExp: false,
2425
loadSchema: async (uri) => {
25-
// Check cache first to prevent infinite loops
26+
const release = await fetchSchemaMutex.acquire();
27+
2628
if (schemaCache.has(uri)) {
2729
log.info(`Loading cached schema: ${uri}`);
28-
29-
try {
30-
const schema = await schemaCache.get(uri)!;
31-
return schema;
32-
} catch {
33-
schemaCache.delete(uri);
34-
}
30+
return schemaCache.get(uri)!;
3531
}
3632

37-
log.info(`Loading external schema: ${uri}`);
38-
let fetchSchema: Promise<object>;
39-
4033
try {
41-
if (uri.includes("adaptive-card.json")) {
42-
// Special handling for Adaptive Card schema to fix unsupported "id" property
43-
// According to the JSON Schema spec Draft 06 (used by Adaptive Card schema),
44-
// "$id" should be used instead of "id"
45-
fetchSchema = fetchCdn(uri)
46-
.then((response) => {
47-
if ("id" in response && typeof response.id === "string") {
48-
const typedResponse = response as Record<string, unknown>;
49-
typedResponse.$id = response.id;
50-
delete typedResponse.id;
51-
}
52-
return response;
53-
});
54-
} else {
55-
fetchSchema = fetchCdn(uri);
34+
log.info(`Loading external schema: ${uri}`);
35+
const schema = await fetchCdn(uri) as AnySchemaObject;
36+
37+
// Special handling for Adaptive Card schema to fix unsupported "id" property
38+
// According to the JSON Schema spec Draft 06 (used by Adaptive Card schema),
39+
// "$id" should be used instead of "id"
40+
if (uri.includes("adaptive-card.json") && typeof schema.id === "string") {
41+
schema.$id = schema.id;
42+
delete schema.id;
5643
}
5744

58-
schemaCache.set(uri, fetchSchema);
59-
return fetchSchema;
45+
schemaCache.set(uri, schema);
46+
47+
return schema;
6048
} catch (error) {
6149
log.warn(`Failed to load external schema ${uri}:` +
6250
`${error instanceof Error ? error.message : String(error)}`);
6351

6452
throw error;
53+
} finally {
54+
release();
6555
}
6656
},
6757
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"type": "object",
4+
"required": ["_version", "sap.app"],
5+
"properties": {
6+
"_version": {
7+
"type": "string"
8+
},
9+
"sap.app": {
10+
"type": "object",
11+
"required": ["id", "type", "applicationVersion"],
12+
"properties": {
13+
"id": {
14+
"type": "string"
15+
},
16+
"type": {
17+
"type": "string"
18+
},
19+
"title": {
20+
"type": "string"
21+
},
22+
"description": {
23+
"type": "string"
24+
},
25+
"applicationVersion": {
26+
"type": "object",
27+
"required": ["version"],
28+
"properties": {
29+
"version": {
30+
"type": "string"
31+
}
32+
}
33+
},
34+
"dataSources": {
35+
"type": "object"
36+
}
37+
}
38+
},
39+
"sap.ui": {
40+
"type": "object"
41+
},
42+
"sap.ui5": {
43+
"type": "object"
44+
}
45+
}
46+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"_version": "1.59.0",
3+
"sap.app": {
4+
"id": "com.example.app",
5+
"type": "application",
6+
"applicationVersion": {
7+
"version": "1.0.0"
8+
}
9+
},
10+
"sap.ui": {
11+
"technology": "UI5",
12+
"deviceTypes": {
13+
"desktop": true,
14+
"tablet": true,
15+
"phone": true
16+
}
17+
},
18+
"sap.ui5": {
19+
"dependencies": {
20+
"minUI5Version": "1.120.0",
21+
"libs": {
22+
"sap.m": {}
23+
}
24+
}
25+
}
26+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import anyTest, {TestFn} from "ava";
2+
import * as sinon from "sinon";
3+
import esmock from "esmock";
4+
import {readFile} from "fs/promises";
5+
import path from "path";
6+
import {fileURLToPath} from "url";
7+
8+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
9+
const fixturesPath = path.join(__dirname, "..", "..", "..", "fixtures", "manifest_validation");
10+
11+
const test = anyTest as TestFn<{
12+
sinon: sinon.SinonSandbox;
13+
runValidation: typeof import("../../../../src/tools/run_manifest_validation/runValidation.js").default;
14+
}>;
15+
16+
test.beforeEach(async (t) => {
17+
t.context.sinon = sinon.createSandbox();
18+
19+
const schemaFixture = await readFile(path.join(fixturesPath, "schema.json"), "utf-8");
20+
const getManifestSchemaStub = t.context.sinon.stub().resolves(JSON.parse(schemaFixture));
21+
22+
// Import the runValidation function
23+
t.context.runValidation = (await esmock(
24+
"../../../../src/tools/run_manifest_validation/runValidation.js", {
25+
"../../../../src/utils/ui5Manifest.js": {
26+
getManifestSchema: getManifestSchemaStub,
27+
},
28+
}
29+
)).default;
30+
});
31+
32+
test.afterEach.always((t) => {
33+
t.context.sinon.restore();
34+
});
35+
36+
test("runValidation successfully validates valid manifest", async (t) => {
37+
const {runValidation} = t.context;
38+
39+
const result = await runValidation(path.join(fixturesPath, "valid-manifest.json"));
40+
41+
t.deepEqual(result, {
42+
isValid: true,
43+
errors: [],
44+
});
45+
});

0 commit comments

Comments
 (0)