Skip to content

Commit 57bbaa6

Browse files
authored
Merge pull request #54 from PMDevSolutions/44-add-test-coverage-for-critical-automation-scripts
test(scripts): add test coverage for critical automation scripts
2 parents 6dc9efb + b47895d commit 57bbaa6

20 files changed

+3714
-9
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ jobs:
201201
run: pnpm install --frozen-lockfile
202202

203203
- name: Run script tests
204-
run: pnpm vitest run scripts/__tests__/ --reporter=verbose
204+
run: pnpm vitest run --config scripts/__tests__/vitest.config.js scripts/__tests__/ --reporter=verbose
205205

206206
# ── Lint & format check (needs Node + project with eslint/prettier) ───
207207
lint:
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
2+
import { execFileSync } from "child_process";
3+
import { join, dirname } from "path";
4+
import { fileURLToPath } from "url";
5+
import { mkdirSync, writeFileSync, rmSync, existsSync, readdirSync } from "fs";
6+
7+
const __dirname = dirname(fileURLToPath(import.meta.url));
8+
const SCRIPT = join(__dirname, "..", "audit-cross-browser-css.sh");
9+
10+
let counter = 0;
11+
12+
function createTmpDir() {
13+
counter++;
14+
const dir = join(__dirname, "fixtures", `audit-css-${counter}-${Date.now()}`);
15+
mkdirSync(dir, { recursive: true });
16+
return dir;
17+
}
18+
19+
function run(cwd, args = []) {
20+
try {
21+
const stdout = execFileSync("bash", [SCRIPT, ...args], {
22+
encoding: "utf-8",
23+
timeout: 15000,
24+
cwd,
25+
});
26+
return { stdout, exitCode: 0 };
27+
} catch (err) {
28+
return {
29+
stdout: err.stdout || "",
30+
stderr: err.stderr || "",
31+
exitCode: err.status,
32+
};
33+
}
34+
}
35+
36+
afterAll(() => {
37+
const fixturesDir = join(__dirname, "fixtures");
38+
if (existsSync(fixturesDir)) {
39+
try {
40+
for (const entry of readdirSync(fixturesDir)) {
41+
if (entry.startsWith("audit-css-")) {
42+
rmSync(join(fixturesDir, entry), { recursive: true, force: true });
43+
}
44+
}
45+
} catch {
46+
// Ignore cleanup errors
47+
}
48+
}
49+
});
50+
51+
describe("audit-cross-browser-css.sh — clean project", () => {
52+
let dir;
53+
54+
beforeAll(() => {
55+
dir = createTmpDir();
56+
mkdirSync(join(dir, "src"), { recursive: true });
57+
writeFileSync(
58+
join(dir, "src", "styles.css"),
59+
`.button {
60+
background: var(--primary);
61+
border-radius: 4px;
62+
}`,
63+
);
64+
});
65+
66+
it("reports no issues on clean CSS", () => {
67+
const result = run(dir, [join(dir, "src")]);
68+
expect(result.exitCode).toBe(0);
69+
expect(result.stdout).toContain("Issues found: 0");
70+
expect(result.stdout).toContain("All clear");
71+
});
72+
});
73+
74+
describe("audit-cross-browser-css.sh — webkit prefix detection", () => {
75+
let dir;
76+
77+
beforeAll(() => {
78+
dir = createTmpDir();
79+
mkdirSync(join(dir, "src"), { recursive: true });
80+
writeFileSync(
81+
join(dir, "src", "app.css"),
82+
`.gradient {
83+
-webkit-linear-gradient(top, red, blue);
84+
background: linear-gradient(top, red, blue);
85+
}`,
86+
);
87+
});
88+
89+
it("detects -webkit- prefixed properties", () => {
90+
const result = run(dir, [join(dir, "src")]);
91+
expect(result.stdout).toContain("-webkit-");
92+
expect(result.stdout).toContain("Vendor prefix");
93+
});
94+
});
95+
96+
describe("audit-cross-browser-css.sh — backdrop-filter without prefix", () => {
97+
let dir;
98+
99+
beforeAll(() => {
100+
dir = createTmpDir();
101+
mkdirSync(join(dir, "src"), { recursive: true });
102+
writeFileSync(
103+
join(dir, "src", "overlay.css"),
104+
`.overlay {
105+
backdrop-filter: blur(10px);
106+
}`,
107+
);
108+
});
109+
110+
it("detects backdrop-filter without -webkit- prefix", () => {
111+
const result = run(dir, [join(dir, "src")]);
112+
expect(result.stdout).toContain("backdrop-filter");
113+
expect(result.stdout).toContain("Safari");
114+
});
115+
});
116+
117+
describe("audit-cross-browser-css.sh — :focus without :focus-visible", () => {
118+
let dir;
119+
120+
beforeAll(() => {
121+
dir = createTmpDir();
122+
mkdirSync(join(dir, "src"), { recursive: true });
123+
writeFileSync(
124+
join(dir, "src", "forms.css"),
125+
`input:focus {
126+
outline: 2px solid blue;
127+
}`,
128+
);
129+
});
130+
131+
it("flags :focus usage without :focus-visible", () => {
132+
const result = run(dir, [join(dir, "src")]);
133+
expect(result.stdout).toContain(":focus");
134+
expect(result.stdout).toContain(":focus-visible");
135+
});
136+
});
137+
138+
describe("audit-cross-browser-css.sh — summary with issue count", () => {
139+
let dir;
140+
141+
beforeAll(() => {
142+
dir = createTmpDir();
143+
mkdirSync(join(dir, "src"), { recursive: true });
144+
writeFileSync(
145+
join(dir, "src", "mixed.css"),
146+
`.a { -webkit-transition: all 0.3s; }
147+
.b:focus { outline: none; }
148+
.c { backdrop-filter: blur(5px); }`,
149+
);
150+
});
151+
152+
it("counts total issues in summary", () => {
153+
const result = run(dir, [join(dir, "src")]);
154+
expect(result.stdout).toContain("Issues found:");
155+
// Should have at least 2 issues (webkit prefix + focus or backdrop)
156+
const match = result.stdout.match(/Issues found: (\d+)/);
157+
expect(match).not.toBeNull();
158+
expect(parseInt(match[1], 10)).toBeGreaterThanOrEqual(2);
159+
});
160+
});

scripts/__tests__/canva-pipeline.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect } from "vitest";
2-
import { readFileSync, existsSync } from "fs";
2+
import { readFileSync } from "fs";
33
import { join, dirname } from "path";
44
import { fileURLToPath } from "url";
55

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { describe, it, expect } from "vitest";
2+
import { execFileSync } from "child_process";
3+
import { join, dirname } from "path";
4+
import { fileURLToPath } from "url";
5+
6+
const __dirname = dirname(fileURLToPath(import.meta.url));
7+
const SCRIPT = join(__dirname, "..", "check-dead-code.sh");
8+
const PROJECT_ROOT = join(__dirname, "..", "..");
9+
10+
/**
11+
* Tests for check-dead-code.sh
12+
*
13+
* Note: This script uses PROJECT_ROOT derived from its own location and cd's into it,
14+
* so it always runs against the actual project. Tests verify CLI behavior and flag parsing.
15+
*/
16+
17+
function run(args = []) {
18+
try {
19+
const stdout = execFileSync("bash", [SCRIPT, ...args], {
20+
encoding: "utf-8",
21+
timeout: 60000,
22+
cwd: PROJECT_ROOT,
23+
});
24+
return { stdout, exitCode: 0 };
25+
} catch (err) {
26+
return {
27+
stdout: err.stdout || "",
28+
stderr: err.stderr || "",
29+
exitCode: err.status,
30+
};
31+
}
32+
}
33+
34+
describe("check-dead-code.sh — help flag", () => {
35+
it("shows usage and exits 0", () => {
36+
const result = run(["--help"]);
37+
expect(result.exitCode).toBe(0);
38+
expect(result.stdout).toContain("Usage:");
39+
expect(result.stdout).toContain("--json");
40+
});
41+
});
42+
43+
describe("check-dead-code.sh — unknown flag", () => {
44+
it("exits 1 on unknown flag", () => {
45+
const result = run(["--bogus"]);
46+
expect(result.exitCode).toBe(1);
47+
expect(result.stdout).toContain("Unknown flag");
48+
});
49+
});
50+
51+
describe("check-dead-code.sh — runs dead code detection", () => {
52+
it("outputs Dead Code Detection header", { timeout: 120000 }, () => {
53+
const result = run([]);
54+
// Should show the detection header (may pass or fail depending on knip findings)
55+
expect(result.stdout).toContain("Dead Code Detection");
56+
});
57+
});
58+
59+
describe("check-dead-code.sh — JSON output", () => {
60+
it("returns valid JSON with --json flag", { timeout: 120000 }, () => {
61+
const result = run(["--json"]);
62+
const parsed = JSON.parse(result.stdout.trim());
63+
expect(parsed).toHaveProperty("status");
64+
expect(["pass", "fail", "skipped"]).toContain(parsed.status);
65+
});
66+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, it, expect } from "vitest";
2+
import { execFileSync } from "child_process";
3+
import { join, dirname } from "path";
4+
import { fileURLToPath } from "url";
5+
6+
const __dirname = dirname(fileURLToPath(import.meta.url));
7+
const SCRIPT = join(__dirname, "..", "check-responsive.sh");
8+
9+
function run(args = []) {
10+
try {
11+
const stdout = execFileSync("bash", [SCRIPT, ...args], {
12+
encoding: "utf-8",
13+
timeout: 15000,
14+
});
15+
return { stdout, exitCode: 0 };
16+
} catch (err) {
17+
return {
18+
stdout: err.stdout || "",
19+
stderr: err.stderr || "",
20+
exitCode: err.status,
21+
};
22+
}
23+
}
24+
25+
describe("check-responsive.sh — help flag", () => {
26+
it("shows usage and exits 0", () => {
27+
const result = run(["--help"]);
28+
expect(result.exitCode).toBe(0);
29+
expect(result.stdout).toContain("Usage:");
30+
expect(result.stdout).toContain("url");
31+
expect(result.stdout).toContain("output-dir");
32+
expect(result.stdout).toContain("breakpoints");
33+
});
34+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { describe, it, expect } from "vitest";
2+
import { execFileSync } from "child_process";
3+
import { join, dirname } from "path";
4+
import { fileURLToPath } from "url";
5+
6+
const __dirname = dirname(fileURLToPath(import.meta.url));
7+
const SCRIPT = join(__dirname, "..", "check-security.sh");
8+
const PROJECT_ROOT = join(__dirname, "..", "..");
9+
10+
/**
11+
* Tests for check-security.sh
12+
*
13+
* Note: This script uses PROJECT_ROOT derived from its own location and cd's into it,
14+
* so it always runs against the actual project. Tests verify CLI behavior and flag parsing.
15+
* Tests that run pnpm audit need extended timeouts.
16+
*/
17+
18+
function run(args = []) {
19+
try {
20+
const stdout = execFileSync("bash", [SCRIPT, ...args], {
21+
encoding: "utf-8",
22+
timeout: 60000,
23+
cwd: PROJECT_ROOT,
24+
});
25+
return { stdout, exitCode: 0 };
26+
} catch (err) {
27+
return {
28+
stdout: err.stdout || "",
29+
stderr: err.stderr || "",
30+
exitCode: err.status,
31+
};
32+
}
33+
}
34+
35+
describe("check-security.sh — help flag", () => {
36+
it("shows usage and exits 0", () => {
37+
const result = run(["--help"]);
38+
expect(result.exitCode).toBe(0);
39+
expect(result.stdout).toContain("Usage:");
40+
expect(result.stdout).toContain("--level");
41+
expect(result.stdout).toContain("--no-fail");
42+
expect(result.stdout).toContain("--json");
43+
});
44+
});
45+
46+
describe("check-security.sh — runs audit", { timeout: 120000 }, () => {
47+
it("outputs Security Audit header and summary", () => {
48+
const result = run(["--no-fail"]);
49+
expect(result.exitCode).toBe(0);
50+
expect(result.stdout).toContain("=== Security Audit ===");
51+
expect(result.stdout).toContain("Running pnpm audit");
52+
expect(result.stdout).toContain("Scanning for security anti-patterns");
53+
expect(result.stdout).toContain("Checking for outdated packages");
54+
expect(result.stdout).toContain("=== Summary ===");
55+
});
56+
57+
it("exits 0 with --no-fail regardless of issues", () => {
58+
const result = run(["--no-fail"]);
59+
expect(result.exitCode).toBe(0);
60+
});
61+
});
62+
63+
describe("check-security.sh — JSON output", { timeout: 120000 }, () => {
64+
it("returns valid JSON with --json --no-fail", () => {
65+
const result = run(["--json", "--no-fail"]);
66+
expect(result.exitCode).toBe(0);
67+
const parsed = JSON.parse(result.stdout.trim());
68+
expect(parsed).toHaveProperty("status");
69+
expect(parsed).toHaveProperty("auditLevel");
70+
expect(parsed).toHaveProperty("issueCount");
71+
expect(parsed).toHaveProperty("antiPatternCount");
72+
expect(parsed).toHaveProperty("envInGitignore");
73+
expect(parsed).toHaveProperty("hasVulnerabilities");
74+
expect(parsed).toHaveProperty("hasOutdatedPackages");
75+
});
76+
77+
it("auditLevel defaults to moderate", () => {
78+
const result = run(["--json", "--no-fail"]);
79+
const parsed = JSON.parse(result.stdout.trim());
80+
expect(parsed.auditLevel).toBe("moderate");
81+
});
82+
});
83+
84+
describe("check-security.sh — --level flag", { timeout: 120000 }, () => {
85+
it("accepts critical level", () => {
86+
const result = run(["--level", "critical", "--no-fail"]);
87+
expect(result.exitCode).toBe(0);
88+
expect(result.stdout).toContain("critical");
89+
});
90+
});

0 commit comments

Comments
 (0)