Refactoring the action from a composite to Node action. This change improves security through pinning of packages as well as better test-ability and error handling. Reviewed-on: #47 Co-authored-by: Timo Behrendt <t.behrendt@t00n.de> Co-committed-by: Timo Behrendt <t.behrendt@t00n.de>
This commit was merged in pull request #47.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
import { run } from "./main";
|
||||
|
||||
run();
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import * as core from "@actions/core";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { validateJson } from "./validation";
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
try {
|
||||
const jsonFilePath = core.getInput("json-file");
|
||||
core.info(`Validating JSON file: ${jsonFilePath}`);
|
||||
|
||||
const json = readFileSync(jsonFilePath, "utf8");
|
||||
|
||||
await validateJson(json);
|
||||
|
||||
core.info("JSON file is valid");
|
||||
} catch (error) {
|
||||
core.setFailed(`Error validating JSON file: ${error}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { validateJson } from "./validation";
|
||||
|
||||
global.fetch = vi.fn();
|
||||
|
||||
const validSchema = JSON.stringify({
|
||||
$id: "https://t00n.de/schema.json",
|
||||
title:
|
||||
"JSON schema for Renovate 43.244.4 config files (https://renovatebot.com/)",
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
$schema: {
|
||||
type: "string",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["name"],
|
||||
additionalProperties: false,
|
||||
});
|
||||
|
||||
describe("validateJson()", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("throws an error if the JSON file is not valid JSON", async () => {
|
||||
const jsonFile = {
|
||||
despiteThisBeingValidJson: "toString() breaks it, making it invalid",
|
||||
}.toString();
|
||||
await expect(validateJson(jsonFile)).rejects.toThrow(
|
||||
'"[object Object]" is not valid JSON',
|
||||
);
|
||||
});
|
||||
|
||||
it("throws an error if the JSON file does not contain a $schema property", async () => {
|
||||
const jsonFile = JSON.stringify({
|
||||
validJson: true,
|
||||
});
|
||||
await expect(validateJson(jsonFile)).rejects.toThrow(
|
||||
"No schema found in JSON file.",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws an error if the schema cannot be fetched", async () => {
|
||||
vi.mocked(fetch).mockRejectedValue(new Error("Failed to fetch schema"));
|
||||
const jsonFile = JSON.stringify({ $schema: "http://t00n.de/404.json" });
|
||||
await expect(validateJson(jsonFile)).rejects.toThrow(
|
||||
"Failed to fetch schema",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws an error if the schema file is not valid", async () => {
|
||||
vi.mocked(fetch).mockResolvedValue(
|
||||
// @ts-expect-error - mock response
|
||||
{
|
||||
ok: true,
|
||||
text: async () =>
|
||||
({
|
||||
despiteThisBeingValidJson:
|
||||
"toString() breaks it, making it invalid",
|
||||
}).toString(),
|
||||
},
|
||||
);
|
||||
|
||||
const jsonFile = JSON.stringify({
|
||||
$schema: "https://json-schema.org/draft-07/schema",
|
||||
});
|
||||
await expect(validateJson(jsonFile)).rejects.toThrow(
|
||||
'"[object Object]" is not valid JSON',
|
||||
);
|
||||
});
|
||||
|
||||
it("throws an error if the JSON file does not match the schema", async () => {
|
||||
vi.mocked(fetch).mockResolvedValue(
|
||||
// @ts-expect-error - mock response
|
||||
{
|
||||
ok: true,
|
||||
text: async () => validSchema,
|
||||
},
|
||||
);
|
||||
|
||||
const jsonFile = JSON.stringify({
|
||||
$schema: "https://json-schema.org/draft-07/schema",
|
||||
notTheExpectedField: 42,
|
||||
});
|
||||
await expect(validateJson(jsonFile)).rejects.toThrow(
|
||||
"JSON file is not valid: No errors",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not throw an error if the JSON file is valid", async () => {
|
||||
vi.mocked(fetch).mockResolvedValue(
|
||||
// @ts-expect-error - mock response
|
||||
{
|
||||
ok: true,
|
||||
text: async () => validSchema,
|
||||
},
|
||||
);
|
||||
|
||||
const jsonFile = JSON.stringify({
|
||||
$schema: "https://t00n.de/schema.json",
|
||||
name: "John Doe",
|
||||
});
|
||||
await expect(validateJson(jsonFile)).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import Ajv from "ajv";
|
||||
|
||||
export const validateJson = async (jsonFileContent: string): Promise<void> => {
|
||||
const jsonObject = JSON.parse(jsonFileContent);
|
||||
|
||||
const schema = jsonObject["$schema"];
|
||||
if (!schema) {
|
||||
throw new Error("No schema found in JSON file.");
|
||||
}
|
||||
|
||||
const schemaUrl = new URL(schema);
|
||||
|
||||
const schemaContent = await fetch(schemaUrl.toString());
|
||||
const schemaText = await schemaContent.text();
|
||||
const schemaObject = JSON.parse(schemaText);
|
||||
|
||||
const validator = new Ajv({
|
||||
strict: false,
|
||||
});
|
||||
const validate = validator.compile(schemaObject);
|
||||
const isValid = validate(jsonObject);
|
||||
if (!isValid) {
|
||||
throw new Error(`JSON file is not valid: ${validator.errorsText()}`);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user