-
Notifications
You must be signed in to change notification settings - Fork 0
fix: repair broken build/CI (Tailwind v4 + stale lockfile) and add test suite #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { describe, test, expect } from "bun:test"; | ||
| import { getRecommendedProvider } from "../packages/deployer/src/detector.js"; | ||
| import type { ProjectConfig } from "../packages/deployer/src/types.js"; | ||
|
|
||
| function makeConfig(type: ProjectConfig["type"]): ProjectConfig { | ||
| return { | ||
| type, | ||
| runtime: "node", | ||
| runtimeVersion: "22", | ||
| framework: type, | ||
| buildCommand: "build", | ||
| startCommand: "start", | ||
| outputDir: "dist", | ||
| port: 3000, | ||
| envVars: [], | ||
| hasDatabase: false, | ||
| hasDocker: false, | ||
| hasCICD: false, | ||
| packageManager: "npm", | ||
| }; | ||
| } | ||
|
|
||
| describe("getRecommendedProvider", () => { | ||
| test("recommends vercel for Next.js", () => { | ||
| expect(getRecommendedProvider(makeConfig("nextjs"))).toBe("vercel"); | ||
| }); | ||
|
|
||
| test("recommends netlify for static frontends", () => { | ||
| expect(getRecommendedProvider(makeConfig("react"))).toBe("netlify"); | ||
| expect(getRecommendedProvider(makeConfig("vue"))).toBe("netlify"); | ||
| }); | ||
|
|
||
| test("recommends docker for compiled backends", () => { | ||
| expect(getRecommendedProvider(makeConfig("go"))).toBe("docker"); | ||
| expect(getRecommendedProvider(makeConfig("rust"))).toBe("docker"); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { describe, test, expect } from "bun:test"; | ||
| import { getBuiltinRules, filterRules, createRule } from "../packages/guardian/src/rules.js"; | ||
|
|
||
| describe("guardian rules", () => { | ||
| test("ships a non-empty set of built-in rules with unique ids", () => { | ||
| const rules = getBuiltinRules(); | ||
| expect(rules.length).toBeGreaterThanOrEqual(14); | ||
| const ids = rules.map((r) => r.id); | ||
| expect(new Set(ids).size).toBe(ids.length); | ||
| }); | ||
|
|
||
| test("getBuiltinRules returns a fresh copy each call", () => { | ||
| const a = getBuiltinRules(); | ||
| a.pop(); | ||
| expect(getBuiltinRules().length).toBeGreaterThan(a.length); | ||
| }); | ||
|
|
||
| test("filterRules narrows by severity", () => { | ||
| const critical = filterRules(getBuiltinRules(), { severity: "critical" }); | ||
| expect(critical.length).toBeGreaterThan(0); | ||
| expect(critical.every((r) => r.severity === "critical")).toBe(true); | ||
| }); | ||
|
|
||
| test("filterRules narrows by category", () => { | ||
| const security = filterRules(getBuiltinRules(), { category: "security" }); | ||
| expect(security.every((r) => r.category === "security")).toBe(true); | ||
| }); | ||
|
Comment on lines
+24
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid a vacuous pass in the category filter test. At Line 26, Suggested patch test("filterRules narrows by category", () => {
const security = filterRules(getBuiltinRules(), { category: "security" });
+ expect(security.length).toBeGreaterThan(0);
expect(security.every((r) => r.category === "security")).toBe(true);
});🤖 Prompt for AI Agents |
||
|
|
||
| test("filterRules excludes disabled rules unless asked", () => { | ||
| const rules = [ | ||
| createRule({ id: "T1", name: "on", pattern: "a", message: "m", enabled: true }), | ||
| createRule({ id: "T2", name: "off", pattern: "b", message: "m", enabled: false }), | ||
| ]; | ||
| expect(filterRules(rules)).toHaveLength(1); | ||
| expect(filterRules(rules, { enabledOnly: false })).toHaveLength(2); | ||
| }); | ||
|
|
||
| test("createRule applies sensible defaults", () => { | ||
| const rule = createRule({ id: "X1", name: "x", pattern: "p", message: "m" }); | ||
| expect(rule.severity).toBe("minor"); | ||
| expect(rule.category).toBe("best-practice"); | ||
| expect(rule.enabled).toBe(true); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import { describe, test, expect } from "bun:test"; | ||
| import { mkdtempSync, writeFileSync } from "fs"; | ||
| import { tmpdir } from "os"; | ||
| import { join } from "path"; | ||
| import { toJSON, toMarkdown, toSARIF } from "../packages/scanner/src/reporter.js"; | ||
| import { detectEcosystem } from "../packages/scanner/src/scanner.js"; | ||
| import type { ScanResult } from "../packages/scanner/src/types.js"; | ||
|
|
||
| const sampleResult: ScanResult = { | ||
| projectName: "demo", | ||
| projectPath: "/tmp/demo", | ||
| timestamp: new Date("2026-01-01T00:00:00Z").toISOString(), | ||
| duration: 42, | ||
| ecosystem: "npm", | ||
| totalDependencies: 3, | ||
| vulnerablePackages: 1, | ||
| vulnerabilities: [ | ||
| { | ||
| id: "GHSA-xxxx-yyyy-zzzz", | ||
| package: "leftpad", | ||
| version: "1.0.0", | ||
| severity: "high", | ||
| title: "Prototype pollution", | ||
| description: "A prototype pollution vulnerability.", | ||
| fixedIn: "1.0.1", | ||
| url: "https://example.com/advisory", | ||
| }, | ||
| ], | ||
| dependencies: [], | ||
| summary: { critical: 0, high: 1, medium: 0, low: 0, info: 0, total: 1, score: 85 }, | ||
| }; | ||
|
|
||
| describe("scanner reporters", () => { | ||
| test("toJSON round-trips the result", () => { | ||
| const parsed = JSON.parse(toJSON(sampleResult)); | ||
| expect(parsed.projectName).toBe("demo"); | ||
| expect(parsed.summary.score).toBe(85); | ||
| expect(parsed.vulnerabilities).toHaveLength(1); | ||
| }); | ||
|
|
||
| test("toMarkdown includes the score and vulnerability id", () => { | ||
| const md = toMarkdown(sampleResult); | ||
| expect(md).toContain("# NexusForge Security Report"); | ||
| expect(md).toContain("85/100"); | ||
| expect(md).toContain("GHSA-xxxx-yyyy-zzzz"); | ||
| }); | ||
|
|
||
| test("toSARIF emits valid SARIF 2.1.0 with one result", () => { | ||
| const sarif = JSON.parse(toSARIF(sampleResult)); | ||
| expect(sarif.version).toBe("2.1.0"); | ||
| expect(sarif.runs[0].tool.driver.name).toBe("NexusForge Scanner"); | ||
| expect(sarif.runs[0].results).toHaveLength(1); | ||
| // high severity maps to an "error" level | ||
| expect(sarif.runs[0].results[0].level).toBe("error"); | ||
| expect(sarif.runs[0].results[0].ruleId).toBe("GHSA-xxxx-yyyy-zzzz"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("detectEcosystem", () => { | ||
| test("detects npm from package.json", () => { | ||
| const dir = mkdtempSync(join(tmpdir(), "nxf-")); | ||
| writeFileSync(join(dir, "package.json"), "{}"); | ||
| expect(detectEcosystem(dir)).toBe("npm"); | ||
| }); | ||
|
|
||
| test("detects cargo from Cargo.toml", () => { | ||
| const dir = mkdtempSync(join(tmpdir(), "nxf-")); | ||
| writeFileSync(join(dir, "Cargo.toml"), ""); | ||
| expect(detectEcosystem(dir)).toBe("cargo"); | ||
| }); | ||
|
|
||
| test("returns unknown for an empty directory", () => { | ||
| const dir = mkdtempSync(join(tmpdir(), "nxf-")); | ||
| expect(detectEcosystem(dir)).toBe("unknown"); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import { describe, test, expect } from "bun:test"; | ||
| import { | ||
| EventBus, | ||
| HookRegistry, | ||
| definePlugin, | ||
| createPluginContext, | ||
| } from "../packages/sdk/src/index.js"; | ||
|
|
||
| describe("EventBus", () => { | ||
| test("delivers payloads to subscribers", async () => { | ||
| const bus = new EventBus(); | ||
| const received: number[] = []; | ||
| bus.on<number>("ping", (n) => received.push(n)); | ||
|
|
||
| await bus.emit("ping", 1); | ||
| await bus.emit("ping", 2); | ||
|
|
||
| expect(received).toEqual([1, 2]); | ||
| }); | ||
|
|
||
| test("once() only fires a single time", async () => { | ||
| const bus = new EventBus(); | ||
| let calls = 0; | ||
| bus.once("boot", () => calls++); | ||
|
|
||
| await bus.emit("boot"); | ||
| await bus.emit("boot"); | ||
|
|
||
| expect(calls).toBe(1); | ||
| expect(bus.listenerCount("boot")).toBe(0); | ||
| }); | ||
|
|
||
| test("the unsubscribe handle removes the listener", async () => { | ||
| const bus = new EventBus(); | ||
| let calls = 0; | ||
| const off = bus.on("tick", () => calls++); | ||
|
|
||
| await bus.emit("tick"); | ||
| off(); | ||
| await bus.emit("tick"); | ||
|
|
||
| expect(calls).toBe(1); | ||
| }); | ||
|
|
||
| test("a throwing handler does not block other handlers", async () => { | ||
| const bus = new EventBus(); | ||
| let reached = false; | ||
| bus.on("x", () => { | ||
| throw new Error("boom"); | ||
| }); | ||
| bus.on("x", () => { | ||
| reached = true; | ||
| }); | ||
|
|
||
| await bus.emit("x"); | ||
|
|
||
| expect(reached).toBe(true); | ||
| }); | ||
| }); | ||
|
|
||
| describe("HookRegistry", () => { | ||
| test("executes handlers ordered by ascending priority", async () => { | ||
| const registry = new HookRegistry(); | ||
| const order: string[] = []; | ||
| const ctx = createPluginContext("/tmp", "test"); | ||
|
|
||
| registry.register("onInit", "late", () => void order.push("late"), 100); | ||
| registry.register("onInit", "early", () => void order.push("early"), 1); | ||
|
|
||
| await registry.execute("onInit", {}, ctx); | ||
|
|
||
| expect(order).toEqual(["early", "late"]); | ||
| }); | ||
|
|
||
| test("unregister removes a plugin's handlers", async () => { | ||
| const registry = new HookRegistry(); | ||
| registry.register("onInit", "p1", () => {}); | ||
|
|
||
| expect(registry.hasListeners("onInit")).toBe(true); | ||
| registry.unregister("onInit", "p1"); | ||
| expect(registry.hasListeners("onInit")).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe("definePlugin", () => { | ||
| test("builds a manifest and exposes command handlers", () => { | ||
| const plugin = definePlugin({ | ||
| name: "greeter", | ||
| version: "1.0.0", | ||
| description: "says hi", | ||
| permissions: ["fs:read"], | ||
| activate() {}, | ||
| commands: { | ||
| hello: { | ||
| description: "Say hello", | ||
| handler: (args) => `Hello, ${args.name ?? "World"}!`, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| expect(plugin.manifest.name).toBe("greeter"); | ||
| expect(plugin.manifest.permissions).toEqual(["fs:read"]); | ||
| expect(plugin.manifest.commands?.[0]?.name).toBe("hello"); | ||
|
|
||
| const ctx = createPluginContext("/tmp", "greeter"); | ||
| expect(plugin.commands?.hello({ name: "Forge" }, ctx)).toBe("Hello, Forge!"); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { describe, test, expect } from "bun:test"; | ||
| import { getMutatorDescriptions } from "../packages/testgen/src/mutator.js"; | ||
| import { buildTestFileName } from "../packages/testgen/src/generator.js"; | ||
| import { getFrameworkConfig } from "../packages/testgen/src/frameworks.js"; | ||
|
|
||
| describe("testgen mutators", () => { | ||
| test("exposes a description for every mutator type", () => { | ||
| const descriptions = getMutatorDescriptions(); | ||
| expect(Object.keys(descriptions)).toContain("arithmetic"); | ||
| expect(Object.keys(descriptions)).toContain("conditional"); | ||
| for (const value of Object.values(descriptions)) { | ||
| expect(typeof value).toBe("string"); | ||
| expect(value.length).toBeGreaterThan(0); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe("testgen helpers", () => { | ||
| test("buildTestFileName swaps the extension for the test extension", () => { | ||
| expect(buildTestFileName("src/utils.ts", ".test.ts")).toBe("utils.test.ts"); | ||
| expect(buildTestFileName("/abs/path/auth.py", "_test.py")).toBe("auth_test.py"); | ||
| }); | ||
|
|
||
| test("getFrameworkConfig returns the matching framework metadata", () => { | ||
| const vitest = getFrameworkConfig("vitest"); | ||
| expect(vitest.name).toBe("vitest"); | ||
| expect(vitest.testExtension).toBe(".test.ts"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: 0xgetz/nexusforge
Length of output: 306
🏁 Script executed:
Repository: 0xgetz/nexusforge
Length of output: 486
Fix README “source file” counts to match package structure (guardian + total are currently off)
packages/*while excludingnode_modules,.next, anddist:*.ts/*.tsx/*.js/*.json): total 77, guardian 13🤖 Prompt for AI Agents