From 7b02375db2b5bbcdc7420f5f7aa99c8ded4c1efa Mon Sep 17 00:00:00 2001 From: Fares Osman <43153226+fiosman@users.noreply.github.com> Date: Fri, 15 May 2026 15:50:39 -0400 Subject: [PATCH] test: adds unit tests --- .../__tests__/bundle-spectral-ruleset.test.ts | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 packages/insomnia/src/main/__tests__/bundle-spectral-ruleset.test.ts diff --git a/packages/insomnia/src/main/__tests__/bundle-spectral-ruleset.test.ts b/packages/insomnia/src/main/__tests__/bundle-spectral-ruleset.test.ts new file mode 100644 index 0000000000..a1caff9f45 --- /dev/null +++ b/packages/insomnia/src/main/__tests__/bundle-spectral-ruleset.test.ts @@ -0,0 +1,239 @@ +import path from 'node:path'; + +import { beforeEach, describe, expect, it, type MockedFunction, vi } from 'vitest'; + +// Mock fs so no real files are needed. +vi.mock('node:fs', () => ({ + default: { + promises: { + readFile: vi.fn(), + }, + }, +})); + +import fs from 'node:fs'; + +import { bundleSpectralRuleset } from '../bundle-spectral-ruleset'; + +const mockReadFile = vi.mocked(fs.promises.readFile) as MockedFunction<(path: string) => Promise>; + +// Returns the absolute path that bundleSpectralRuleset will resolve for a given fake path. +function abs(fakePath: string) { + return path.resolve(fakePath); +} + +beforeEach(() => { + mockReadFile.mockReset(); +}); + +describe('bundleSpectralRuleset', () => { + it('returns a simple ruleset with no extends unchanged', async () => { + mockReadFile.mockResolvedValueOnce( + ` +rules: + my-rule: + given: "$.info" + severity: warn + then: + function: truthy +`, + ); + + const result = await bundleSpectralRuleset('/fake/ruleset.yaml'); + expect(result).toContain('my-rule'); + expect(result).not.toContain('extends'); + }); + + it('passes through remote URL extends unchanged', async () => { + mockReadFile.mockResolvedValueOnce( + ` +extends: + - "https://example.com/ruleset.yaml" +rules: + my-rule: + given: "$.info" + severity: warn + then: + function: truthy +`, + ); + + const result = await bundleSpectralRuleset('/fake/ruleset.yaml'); + expect(result).toContain('https://example.com/ruleset.yaml'); + expect(result).toContain('my-rule'); + }); + + it('passes through spectral built-in identifier extends unchanged', async () => { + mockReadFile.mockResolvedValueOnce( + ` +extends: "spectral:oas" +rules: + my-rule: + given: "$.info" + severity: warn + then: + function: truthy +`, + ); + + const result = await bundleSpectralRuleset('/fake/ruleset.yaml'); + expect(result).toContain('spectral:oas'); + expect(result).toContain('my-rule'); + }); + + it('flattens a local extends entry, merging child rules into the parent', async () => { + const parentPath = '/fake/parent.yaml'; + const childPath = '/fake/child.yaml'; + + mockReadFile.mockImplementation(async (filePath) => { + if (filePath === abs(parentPath)) { + return ` +extends: + - "./child.yaml" +rules: + parent-rule: + given: "$.info" + severity: warn + then: + function: truthy +`; + } + if (filePath === abs(childPath)) { + return ` +rules: + child-rule: + given: "$.paths" + severity: error + then: + function: truthy +`; + } + throw new Error(`Unexpected readFile call: ${filePath}`); + }); + + const result = await bundleSpectralRuleset(parentPath); + expect(result).toContain('parent-rule'); + expect(result).toContain('child-rule'); + expect(result).not.toContain('./child.yaml'); + }); + + it('parent rule overrides child rule with the same name', async () => { + const parentPath = '/fake/parent.yaml'; + const childPath = '/fake/child.yaml'; + + mockReadFile.mockImplementation(async (filePath) => { + if (filePath === abs(parentPath)) { + return ` +extends: + - "./child.yaml" +rules: + shared-rule: + given: "$.info" + severity: warn + then: + function: truthy +`; + } + if (filePath === abs(childPath)) { + return ` +rules: + shared-rule: + given: "$.paths" + severity: error + then: + function: truthy +`; + } + throw new Error(`Unexpected readFile call: ${filePath}`); + }); + + const result = await bundleSpectralRuleset(parentPath); + // Parent's severity (warn) wins over child's (error). + expect(result).toContain('warn'); + expect(result).not.toContain('error'); + }); + + it('throws on a cycle in extends', async () => { + const aPath = '/fake/a.yaml'; + const bPath = '/fake/b.yaml'; + + mockReadFile.mockImplementation(async (filePath) => { + if (filePath === abs(aPath)) { + return `extends:\n - "./b.yaml"\n`; + } + if (filePath === abs(bPath)) { + return `extends:\n - "./a.yaml"\n`; + } + throw new Error(`Unexpected readFile call: ${filePath}`); + }); + + await expect(bundleSpectralRuleset(aPath)).rejects.toThrow('"extends" cycle detected'); + }); + + it('throws when extends nesting exceeds max depth', async () => { + // 7 levels of nesting exceeds the max depth of 5, so this should throw an error. + const files: Record = {}; + for (let i = 0; i <= 6; i++) { + const next = i < 6 ? `extends:\n - "./depth${i + 1}.yaml"\n` : `rules: {}\n`; + files[abs(`/fake/depth${i}.yaml`)] = next; + } + + mockReadFile.mockImplementation(async (filePath) => { + if (files[filePath]) { + return files[filePath]; + } + throw new Error(`Unexpected readFile call: ${filePath}`); + }); + + await expect(bundleSpectralRuleset('/fake/depth0.yaml')).rejects.toThrow('"extends" nested too deeply'); + }); + + it('throws when extends points to a non-YAML file', async () => { + mockReadFile.mockResolvedValueOnce(`extends:\n - "./rules.txt"\n`); + + await expect(bundleSpectralRuleset('/fake/ruleset.yaml')).rejects.toThrow( + '"extends" target must be a .yaml or .yml file', + ); + }); + + it('throws when an extends entry uses tuple format', async () => { + mockReadFile.mockResolvedValueOnce( + ` +extends: + - - spectral:oas + - recommended +`, + ); + + await expect(bundleSpectralRuleset('/fake/ruleset.yaml')).rejects.toThrow('tuple format'); + }); + + it('throws when the ruleset file is not a YAML object', async () => { + mockReadFile.mockResolvedValueOnce('- item1\n- item2\n'); + + await expect(bundleSpectralRuleset('/fake/ruleset.yaml')).rejects.toThrow('must be an object at the top level'); + }); + + it('deduplicates remote extends from multiple child files', async () => { + const parentPath = '/fake/parent.yaml'; + const childAPath = '/fake/childA.yaml'; + const childBPath = '/fake/childB.yaml'; + + mockReadFile.mockImplementation(async (filePath) => { + if (filePath === abs(parentPath)) { + return `extends:\n - "./childA.yaml"\n - "./childB.yaml"\n`; + } + if (filePath === abs(childAPath)) { + return `extends:\n - "spectral:oas"\n`; + } + if (filePath === abs(childBPath)) { + return `extends:\n - "spectral:oas"\n`; + } + throw new Error(`Unexpected readFile call: ${filePath}`); + }); + + const result = await bundleSpectralRuleset(parentPath); + const matches = (result.match(/spectral:oas/g) ?? []).length; + expect(matches).toBe(1); + }); +});