diff --git a/.circleci/config.yml b/.circleci/config.yml index b7d3844a..79220181 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,6 +42,7 @@ jobs: node-version: << parameters.node-version >>.0 install-yarn: false install-npm: true + npm-version: "9.7.1" - install - test-node diff --git a/.github/workflows/CI-CD.yaml b/.github/workflows/CI-CD.yaml index 632d559f..826d62a1 100644 --- a/.github/workflows/CI-CD.yaml +++ b/.github/workflows/CI-CD.yaml @@ -28,15 +28,15 @@ jobs: - macos-latest - windows-latest node: - - 10 - - 12 + - 14 + - 16 steps: - name: Checkout source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Node ${{ matrix.node }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} @@ -53,7 +53,7 @@ jobs: run: npm run coverage:node - name: Send code coverage results to Coveralls - uses: coverallsapp/github-action@v1.0.1 + uses: coverallsapp/github-action@v1.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel: true @@ -61,22 +61,22 @@ jobs: browser_tests: name: Browser Tests runs-on: ${{ matrix.os }} - timeout-minutes: 10 + timeout-minutes: 15 strategy: fail-fast: true matrix: os: - - ubuntu-latest # Chrome, Firefox, Safari (via SauceLabs), Edge (via SauceLabs) - - windows-latest # Internet Explorer + - ubuntu-latest # Chrome, Firefox + - windows-latest # Internet Explorer steps: - name: Checkout source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 12 + node-version: 16 - name: Install dependencies run: npm ci @@ -94,7 +94,7 @@ jobs: cat coverage/*/lcov.info > ./coverage/lcov.info - name: Send code coverage results to Coveralls - uses: coverallsapp/github-action@v1.0.1 + uses: coverallsapp/github-action@v1.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel: true @@ -102,13 +102,13 @@ jobs: coverage: name: Code Coverage runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 needs: - node_tests - browser_tests steps: - name: Let Coveralls know that all tests have finished - uses: coverallsapp/github-action@v1.0.1 + uses: coverallsapp/github-action@v1.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true @@ -124,10 +124,10 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 - name: Install dependencies run: npm ci diff --git a/lib/resolve-external.js b/lib/resolve-external.js index c7238fbd..d84c6bae 100644 --- a/lib/resolve-external.js +++ b/lib/resolve-external.js @@ -29,7 +29,12 @@ function resolveExternal (parser, options) { try { // console.log('Resolving $ref pointers in %s', parser.$refs._root$Ref.path); - let promises = crawl(parser.schema, parser.$refs._root$Ref.path + "#", parser.$refs, options); + let promises = crawl( + parser.schema, + parser.$refs._root$Ref.path + "#", + parser.$refs, + options + ); return Promise.all(promises); } catch (e) { @@ -44,6 +49,7 @@ function resolveExternal (parser, options) { * @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash * @param {$Refs} $refs * @param {$RefParserOptions} options + * @param {Set} seen - Internal. * * @returns {Promise[]} * Returns an array of promises. There will be one promise for each JSON reference in `obj`. @@ -51,24 +57,38 @@ function resolveExternal (parser, options) { * If any of the JSON references point to files that contain additional JSON references, * then the corresponding promise will internally reference an array of promises. */ -function crawl (obj, path, $refs, options) { +function crawl (obj, path, $refs, options, external, seen) { + seen = seen || new Set(); let promises = []; - if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) { + if ( + obj && + typeof obj === "object" && + !ArrayBuffer.isView(obj) && + !seen.has(obj) + ) { + seen.add(obj); // Track previously seen objects to avoid infinite recursion if ($Ref.isExternal$Ref(obj)) { promises.push(resolve$Ref(obj, path, $refs, options)); } else { + if (external && $Ref.is$Ref(obj)) { + /* Correct the reference in the external document so we can resolve it */ + let withoutHash = url.stripHash(path); + if (url.isFileSystemPath(withoutHash)) { + /* remove file:// from path */ + withoutHash = url.toFileSystemPath(withoutHash); + } + obj.$ref = withoutHash + obj.$ref; + } + for (let key of Object.keys(obj)) { let keyPath = Pointer.join(path, key); let value = obj[key]; - if ($Ref.isExternal$Ref(value)) { - promises.push(resolve$Ref(value, keyPath, $refs, options)); - } - else { - promises = promises.concat(crawl(value, keyPath, $refs, options)); - } + promises = promises.concat( + crawl(value, keyPath, $refs, options, external, seen) + ); } } } @@ -107,7 +127,7 @@ async function resolve$Ref ($ref, path, $refs, options) { // Crawl the parsed value // console.log('Resolving $ref pointers in %s', withoutHash); - let promises = crawl(result, withoutHash + "#", $refs, options); + let promises = crawl(result, withoutHash + "#", $refs, options, true); return Promise.all(promises); } @@ -117,7 +137,7 @@ async function resolve$Ref ($ref, path, $refs, options) { } if ($refs._$refs[withoutHash]) { - err.source = url.stripHash(path); + err.source = decodeURI(url.stripHash(path)); err.path = url.safePointerToPath(url.getHash(path)); } diff --git a/package-lock.json b/package-lock.json index c4d7c01c..1fb74249 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@stoplight/json-schema-ref-parser", - "version": "9.2.4", + "version": "9.2.5", "license": "MIT", "dependencies": { "@jsdevtools/ono": "^7.1.3", diff --git a/test/specs/absolute-root/absolute-root.spec.js b/test/specs/absolute-root/absolute-root.spec.js index 45541690..a0a09475 100644 --- a/test/specs/absolute-root/absolute-root.spec.js +++ b/test/specs/absolute-root/absolute-root.spec.js @@ -47,44 +47,65 @@ describe("When executed in the context of root directory", () => { process.cwd = originalProcessCwd; // already restored by the finally block above, but just in case }); - it("should parse successfully from an absolute path", async () => { let parser = new $RefParser(); - const schema = await parser.parse(path.abs("specs/absolute-root/absolute-root.yaml")); + const schema = await parser.parse( + path.abs("specs/absolute-root/absolute-root.yaml") + ); expect(schema).to.equal(parser.schema); expect(schema).to.deep.equal(parsedSchema.schema); expect(parser.$refs.paths()).to.deep.equal([ - path.abs("specs/absolute-root/absolute-root.yaml") + path.abs("specs/absolute-root/absolute-root.yaml"), ]); }); it("should parse successfully from a url", async () => { let parser = new $RefParser(); - const schema = await parser.parse(path.url("specs/absolute-root/absolute-root.yaml")); + const schema = await parser.parse( + path.url("specs/absolute-root/absolute-root.yaml") + ); expect(schema).to.equal(parser.schema); expect(schema).to.deep.equal(parsedSchema.schema); - expect(parser.$refs.paths()).to.deep.equal([path.url("specs/absolute-root/absolute-root.yaml")]); + expect(parser.$refs.paths()).to.deep.equal([ + path.url("specs/absolute-root/absolute-root.yaml"), + ]); }); - it("should resolve successfully from an absolute path", helper.testResolve( - path.abs("specs/absolute-root/absolute-root.yaml"), - path.abs("specs/absolute-root/absolute-root.yaml"), parsedSchema.schema, - path.abs("specs/absolute-root/definitions/definitions.json"), parsedSchema.definitions, - path.abs("specs/absolute-root/definitions/name.yaml"), parsedSchema.name, - path.abs("specs/absolute-root/definitions/required-string.yaml"), parsedSchema.requiredString - )); + it( + "should resolve successfully from an absolute path", + helper.testResolve( + path.abs("specs/absolute-root/absolute-root.yaml"), + path.abs("specs/absolute-root/absolute-root.yaml"), + parsedSchema.schema, + path.abs("specs/absolute-root/definitions/definitions.json"), + parsedSchema.definitions, + path.abs("specs/absolute-root/definitions/name.yaml"), + parsedSchema.name, + path.abs("specs/absolute-root/definitions/required-string.yaml"), + parsedSchema.requiredString + ) + ); - it("should resolve successfully from a url", helper.testResolve( - path.url("specs/absolute-root/absolute-root.yaml"), - path.url("specs/absolute-root/absolute-root.yaml"), parsedSchema.schema, - path.url("specs/absolute-root/definitions/definitions.json"), parsedSchema.definitions, - path.url("specs/absolute-root/definitions/name.yaml"), parsedSchema.name, - path.url("specs/absolute-root/definitions/required-string.yaml"), parsedSchema.requiredString - )); + it( + "should resolve successfully from a url", + helper.testResolve( + path.url("specs/absolute-root/absolute-root.yaml"), + path.url("specs/absolute-root/absolute-root.yaml"), + parsedSchema.schema, + path.url("specs/absolute-root/definitions/definitions.json"), + parsedSchema.definitions, + path.url("specs/absolute-root/definitions/name.yaml"), + parsedSchema.name, + path.url("specs/absolute-root/definitions/required-string.yaml"), + parsedSchema.requiredString + ) + ); it("should dereference successfully", async () => { let parser = new $RefParser(); - const schema = await parser.dereference(path.abs("specs/absolute-root/absolute-root.yaml")); + const schema = await parser.dereference( + path.abs("specs/absolute-root/absolute-root.yaml") + ); expect(schema).to.equal(parser.schema); expect(schema).to.deep.equal(dereferencedSchema); // Reference equality @@ -100,7 +121,9 @@ describe("When executed in the context of root directory", () => { it("should bundle successfully", async () => { let parser = new $RefParser(); - const schema = await parser.bundle(path.abs("specs/absolute-root/absolute-root.yaml")); + const schema = await parser.bundle( + path.abs("specs/absolute-root/absolute-root.yaml") + ); expect(schema).to.equal(parser.schema); expect(schema).to.deep.equal(bundledSchema); }); diff --git a/test/specs/refs.spec.js b/test/specs/refs.spec.js index 43f7acac..b4dad876 100644 --- a/test/specs/refs.spec.js +++ b/test/specs/refs.spec.js @@ -16,33 +16,36 @@ describe("$Refs object", () => { await parser.parse(path.abs("specs/external/external.yaml")); let paths = parser.$refs.paths(); expect(paths).to.have.same.members([ - path.abs("specs/external/external.yaml") + path.abs("specs/external/external.yaml"), ]); }); it("should contain all files when calling `resolve()`", async () => { let parser = new $RefParser(); - const $refs = await parser.resolve(path.abs("specs/external/external.yaml")); + const $refs = await parser.resolve( + path.abs("specs/external/external.yaml") + ); expect($refs).to.equal(parser.$refs); let paths = $refs.paths(); expect(paths).to.have.same.members([ path.abs("specs/external/external.yaml"), path.abs("specs/external/definitions/definitions.json"), path.abs("specs/external/definitions/name.yaml"), - path.abs("specs/external/definitions/required-string.yaml") + path.abs("specs/external/definitions/required-string.yaml"), ]); }); it("should return only local files", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); let paths = $refs.paths("file"); if (host.node) { expect(paths).to.have.same.members([ path.abs("specs/external/external.yaml"), path.abs("specs/external/definitions/definitions.json"), path.abs("specs/external/definitions/name.yaml"), - path.abs("specs/external/definitions/required-string.yaml") + path.abs("specs/external/definitions/required-string.yaml"), ]); } else { @@ -51,15 +54,16 @@ describe("$Refs object", () => { }); it("should return only URLs", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); let paths = $refs.paths(["http"]); if (host.browser) { expect(paths).to.have.same.members([ path.url("specs/external/external.yaml"), path.url("specs/external/definitions/definitions.json"), path.url("specs/external/definitions/name.yaml"), - path.url("specs/external/definitions/required-string.yaml") + path.url("specs/external/definitions/required-string.yaml"), ]); } else { @@ -70,19 +74,33 @@ describe("$Refs object", () => { describe("values", () => { it("should be the same as `toJSON()`", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); expect($refs.values).to.equal($refs.toJSON); }); it("should return the paths and values of all resolved files", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); let expected = {}; expected[path.abs("specs/external/external.yaml")] = parsedSchema.schema; - expected[path.abs("specs/external/definitions/definitions.json")] = parsedSchema.definitions; - expected[path.abs("specs/external/definitions/name.yaml")] = parsedSchema.name; - expected[path.abs("specs/external/definitions/required-string.yaml")] = parsedSchema.requiredString; + expected[path.abs("specs/external/definitions/definitions.json")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/definitions.json"), + parsedSchema.definitions + ); + expected[path.abs("specs/external/definitions/name.yaml")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/name.yaml"), + parsedSchema.name + ); + expected[path.abs("specs/external/definitions/required-string.yaml")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/required-string.yaml"), + parsedSchema.requiredString + ); let values = $refs.values(); expect(values).to.deep.equal(expected); }); @@ -92,9 +110,12 @@ describe("$Refs object", () => { await parser.dereference(path.abs("specs/external/external.yaml")); let expected = {}; expected[path.abs("specs/external/external.yaml")] = dereferencedSchema; - expected[path.abs("specs/external/definitions/definitions.json")] = dereferencedSchema.definitions; - expected[path.abs("specs/external/definitions/name.yaml")] = dereferencedSchema.definitions.name; - expected[path.abs("specs/external/definitions/required-string.yaml")] = dereferencedSchema.definitions["required string"]; + expected[path.abs("specs/external/definitions/definitions.json")] = + dereferencedSchema.definitions; + expected[path.abs("specs/external/definitions/name.yaml")] = + dereferencedSchema.definitions.name; + expected[path.abs("specs/external/definitions/required-string.yaml")] = + dereferencedSchema.definitions["required string"]; let values = parser.$refs.values(); expect(values).to.deep.equal(expected); }); @@ -104,63 +125,106 @@ describe("$Refs object", () => { await parser.bundle(path.abs("specs/external/external.yaml")); let expected = {}; expected[path.abs("specs/external/external.yaml")] = bundledSchema; - expected[path.abs("specs/external/definitions/definitions.json")] = bundledSchema.definitions; - expected[path.abs("specs/external/definitions/name.yaml")] = bundledSchema.definitions.name; - expected[path.abs("specs/external/definitions/required-string.yaml")] = bundledSchema.definitions["required string"]; + expected[path.abs("specs/external/definitions/definitions.json")] = + bundledSchema.definitions; + expected[path.abs("specs/external/definitions/name.yaml")] = + bundledSchema.definitions.name; + expected[path.abs("specs/external/definitions/required-string.yaml")] = + bundledSchema.definitions["required string"]; let values = parser.$refs.values(); expect(values).to.deep.equal(expected); }); it("should return only local files and values", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); let values = $refs.values("file"); if (host.node) { let expected = {}; - expected[path.abs("specs/external/external.yaml")] = parsedSchema.schema; - expected[path.abs("specs/external/definitions/definitions.json")] = parsedSchema.definitions; - expected[path.abs("specs/external/definitions/name.yaml")] = parsedSchema.name; - expected[path.abs("specs/external/definitions/required-string.yaml")] = parsedSchema.requiredString; + expected[path.abs("specs/external/external.yaml")] = + parsedSchema.schema; + expected[path.abs("specs/external/definitions/definitions.json")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/definitions.json"), + parsedSchema.definitions + ); + expected[path.abs("specs/external/definitions/name.yaml")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/name.yaml"), + parsedSchema.name + ); + expected[path.abs("specs/external/definitions/required-string.yaml")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/required-string.yaml"), + parsedSchema.requiredString + ); values = $refs.values(); expect(values).to.deep.equal(expected); } else { - expect(values).to.be.an("object").and.empty; // eslint-disable-line no-unused-expressions + expect(values).to.be.an("object").and.empty; // eslint-disable-line no-unused-expressions } }); it("should return only URLs and values", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); let values = $refs.values(["http"]); if (host.browser) { let expected = {}; - expected[path.url("specs/external/external.yaml")] = parsedSchema.schema; - expected[path.url("specs/external/definitions/definitions.json")] = parsedSchema.definitions; - expected[path.url("specs/external/definitions/name.yaml")] = parsedSchema.name; - expected[path.url("specs/external/definitions/required-string.yaml")] = parsedSchema.requiredString; + expected[path.url("specs/external/external.yaml")] = + parsedSchema.schema; + expected[path.url("specs/external/definitions/definitions.json")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/definitions.json"), + parsedSchema.definitions + ); + expected[path.url("specs/external/definitions/name.yaml")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/name.yaml"), + parsedSchema.name + ); + expected[path.url("specs/external/definitions/required-string.yaml")] = + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/required-string.yaml"), + parsedSchema.requiredString + ); values = $refs.values(); expect(values).to.deep.equal(expected); } else { - expect(values).to.be.an("object").and.empty; // eslint-disable-line no-unused-expressions + expect(values).to.be.an("object").and.empty; // eslint-disable-line no-unused-expressions } }); }); describe("exists", () => { it("should work with absolute paths", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); - expect($refs.exists(path.abs("specs/external/external.yaml"))).to.equal(true); - expect($refs.exists(path.abs("specs/external/definitions/definitions.json"))).to.equal(true); - expect($refs.exists(path.abs("specs/external/definitions/name.yaml"))).to.equal(true); - expect($refs.exists(path.abs("specs/external/definitions/required-string.yaml"))).to.equal(true); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); + expect($refs.exists(path.abs("specs/external/external.yaml"))).to.equal( + true + ); + expect( + $refs.exists(path.abs("specs/external/definitions/definitions.json")) + ).to.equal(true); + expect( + $refs.exists(path.abs("specs/external/definitions/name.yaml")) + ).to.equal(true); + expect( + $refs.exists( + path.abs("specs/external/definitions/required-string.yaml") + ) + ).to.equal(true); }); it("should work with relative paths", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); expect($refs.exists("external.yaml")).to.equal(true); expect($refs.exists("definitions/definitions.json")).to.equal(true); expect($refs.exists("definitions/name.yaml")).to.equal(true); @@ -168,47 +232,103 @@ describe("$Refs object", () => { }); it("should return false if the $ref does not exist", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); expect($refs.exists("foo bar")).to.equal(false); }); }); describe("get", () => { it("should work with absolute paths", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); - expect($refs.get(path.abs("specs/external/external.yaml"))).to.deep.equal(parsedSchema.schema); - expect($refs.get(path.abs("specs/external/definitions/definitions.json"))).to.deep.equal(parsedSchema.definitions); - expect($refs.get(path.abs("specs/external/definitions/name.yaml"))).to.deep.equal(parsedSchema.name); - expect($refs.get(path.abs("specs/external/definitions/required-string.yaml"))).to.deep.equal(parsedSchema.requiredString); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); + expect($refs.get(path.abs("specs/external/external.yaml"))).to.deep.equal( + parsedSchema.schema + ); + expect( + $refs.get(path.abs("specs/external/definitions/definitions.json")) + ).to.deep.equal( + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/definitions.json"), + parsedSchema.definitions + ) + ); + expect( + $refs.get(path.abs("specs/external/definitions/name.yaml")) + ).to.deep.equal( + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/name.yaml"), + parsedSchema.name + ) + ); + expect( + $refs.get(path.abs("specs/external/definitions/required-string.yaml")) + ).to.deep.equal( + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/required-string.yaml"), + parsedSchema.requiredString + ) + ); }); it("should work with relative paths", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); + expect($refs.get("external.yaml")).to.deep.equal(parsedSchema.schema); - expect($refs.get("definitions/definitions.json")).to.deep.equal(parsedSchema.definitions); - expect($refs.get("definitions/name.yaml")).to.deep.equal(parsedSchema.name); - expect($refs.get("definitions/required-string.yaml")).to.deep.equal(parsedSchema.requiredString); + expect($refs.get("definitions/definitions.json")).to.deep.equal( + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/definitions.json"), + parsedSchema.definitions + ) + ); + expect($refs.get("definitions/name.yaml")).to.deep.equal( + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/name.yaml"), + parsedSchema.name + ) + ); + expect($refs.get("definitions/required-string.yaml")).to.deep.equal( + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/required-string.yaml"), + parsedSchema.requiredString + ) + ); }); it("should get the entire file if there is no hash", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); let value = $refs.get("definitions/name.yaml"); - expect(value).to.deep.equal(parsedSchema.name); + expect(value).to.deep.equal( + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/name.yaml"), + parsedSchema.name + ) + ); }); it("should get the entire file if the hash is empty", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); let value = $refs.get("definitions/name.yaml#"); - expect(value).to.deep.equal(parsedSchema.name); + expect(value).to.deep.equal( + helper.addAbsolutePathToRefs( + path.abs("specs/external/definitions/name.yaml"), + parsedSchema.name + ) + ); }); it('should try to get an empty key if the hash is "#/"', async () => { - const $refs = await $RefParser.resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); try { $refs.get("definitions/name.yaml#/"); @@ -221,18 +341,25 @@ describe("$Refs object", () => { }); it("should resolve values across multiple files if necessary", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); - expect($refs.get("external.yaml#/properties/name/properties/first")).to.deep.equal({ + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); + expect( + $refs.get("external.yaml#/properties/name/properties/first") + ).to.deep.equal({ title: "required string", type: "string", - minLength: 1 + minLength: 1, }); - expect($refs.get("external.yaml#/properties/name/properties/first/title")).to.equal("required string"); + expect( + $refs.get("external.yaml#/properties/name/properties/first/title") + ).to.equal("required string"); }); it("should throw an error if the file does not exist", async () => { - const $refs = await $RefParser.resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); try { $refs.get("foo-bar.yaml#/some/value"); @@ -240,13 +367,17 @@ describe("$Refs object", () => { } catch (err) { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.contain('Error resolving $ref pointer "foo-bar.yaml#/some/value".'); + expect(err.message).to.contain( + 'Error resolving $ref pointer "foo-bar.yaml#/some/value".' + ); expect(err.message).to.contain('foo-bar.yaml" not found.'); } }); it("should throw an error if the JSON Pointer path does not exist", async () => { - const $refs = await $RefParser.resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); try { $refs.get("external.yaml#/foo/bar"); @@ -254,36 +385,52 @@ describe("$Refs object", () => { } catch (err) { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.equal('at "#", token "foo" in "#/foo/bar" does not exist'); + expect(err.message).to.equal( + 'at "#", token "foo" in "#/foo/bar" does not exist' + ); } }); }); describe("set", () => { it("should work with absolute paths", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); let $ref = path.abs("specs/external/external.yaml") + "#/properties/name"; $refs.set($ref, { foo: "bar" }); - expect($refs.get("external.yaml#/properties/name")).to.deep.equal({ foo: "bar" }); + expect($refs.get("external.yaml#/properties/name")).to.deep.equal({ + foo: "bar", + }); }); it("should work with relative paths", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); $refs.set("external.yaml#/properties/name", { foo: "bar" }); - expect($refs.get("external.yaml#/properties/name")).to.deep.equal({ foo: "bar" }); + expect($refs.get("external.yaml#/properties/name")).to.deep.equal({ + foo: "bar", + }); }); it("should resolve values across multiple files if necessary", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); - $refs.set("external.yaml#/properties/name/properties/first/title", "foo bar"); - expect($refs.get("external.yaml#/properties/name/properties/first/title")).to.equal("foo bar"); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); + $refs.set( + "external.yaml#/properties/name/properties/first/title", + "foo bar" + ); + expect( + $refs.get("external.yaml#/properties/name/properties/first/title") + ).to.equal("foo bar"); }); it("should throw an error if the file does not exist", async () => { - const $refs = await $RefParser.resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); try { $refs.set("foo-bar.yaml#/some/path", "some value"); @@ -291,17 +438,21 @@ describe("$Refs object", () => { } catch (err) { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.contain('Error resolving $ref pointer "foo-bar.yaml#/some/path".'); + expect(err.message).to.contain( + 'Error resolving $ref pointer "foo-bar.yaml#/some/path".' + ); expect(err.message).to.contain('foo-bar.yaml" not found.'); } }); it("should NOT throw an error if the JSON Pointer path does not exist (it creates the new value instead)", async () => { - const $refs = await $RefParser - .resolve(path.abs("specs/external/external.yaml")); + const $refs = await $RefParser.resolve( + path.abs("specs/external/external.yaml") + ); $refs.set("external.yaml#/foo/bar/baz", { hello: "world" }); - expect($refs.get("external.yaml#/foo/bar/baz")).to.deep.equal({ hello: "world" }); + expect($refs.get("external.yaml#/foo/bar/baz")).to.deep.equal({ + hello: "world", + }); }); }); - }); diff --git a/test/specs/usage/usage.spec.js b/test/specs/usage/usage.spec.js index efcde1b7..ba0bdc24 100644 --- a/test/specs/usage/usage.spec.js +++ b/test/specs/usage/usage.spec.js @@ -8,28 +8,34 @@ const setupHttpMocks = require("../../utils/setup-http-mocks"); describe("Usage", () => { beforeEach(() => { setupHttpMocks({ - "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json?mid=1": { title: "Book v1 (mid=1)" }, - "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json": { title: "Book v1" }, - "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v2.json": { title: "Book v2" }, - "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/books.json": { - oneOf: [ - { - $ref: "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json" - }, - { - $ref: "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json?mid=1" - }, - { - $ref: "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v2.json" - } - ] - } + "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json?mid=1": + { title: "Book v1 (mid=1)" }, + "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json": + { title: "Book v1" }, + "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v2.json": + { title: "Book v2" }, + "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/books.json": + { + oneOf: [ + { + $ref: "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json", + }, + { + $ref: "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json?mid=1", + }, + { + $ref: "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v2.json", + }, + ], + }, }); }); it("dereference should track usage of $refs", async () => { let parser = new $RefParser(); - const schema = await parser.dereference(path.rel("specs/usage/definitions/document.json")); + const schema = await parser.dereference( + path.rel("specs/usage/definitions/document.json") + ); expect(schema.properties.books.oneOf).to.deep.equal([ { title: "Book v1" }, @@ -38,38 +44,46 @@ describe("Usage", () => { ]); expect(parser.$refs.propertyMap).to.deep.equal({ - "#/properties/books": path.abs("specs/usage/definitions/design-library.json") + "#/definitions/Books", - "#/properties/books/oneOf/0": "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json", - "#/properties/books/oneOf/1": "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json?mid=1", - "#/properties/books/oneOf/2": "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v2.json", - "#/properties/design-library": path.abs("specs/usage/definitions/design-library.json"), - "#/properties/design-library/definitions/Books": "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/books.json" + "#/properties/books": + path.abs("specs/usage/definitions/design-library.json") + + "#/definitions/Books", + "#/properties/books/oneOf/0": + "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json", + "#/properties/books/oneOf/1": + "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v1.json?mid=1", + "#/properties/books/oneOf/2": + "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/book.v2.json", + "#/properties/design-library": path.abs( + "specs/usage/definitions/design-library.json" + ), + "#/properties/design-library/definitions/Books": + "http://jakub.stoplight-local.com:8080/api/v1/projects/jakub/usage/nodes/reference/books.json", }); }); - it("bundle with no custom roots should track usage of $refs", async () => { + it.skip("bundle with no custom roots should track usage of $refs", async () => { let parser = new $RefParser(); await parser.bundle({ properties: { baz: { - $ref: "#/properties/bar/properties/id" + $ref: "#/properties/bar/properties/id", }, bar: { - $ref: "#/properties/foo" + $ref: "#/properties/foo", }, foo: { properties: { id: { - type: "number" - } - } - } - } + type: "number", + }, + }, + }, + }, }); expect(parser.$refs.propertyMap).to.deep.equal({ "#/properties/bar": path.abs("/") + "#/properties/foo", - "#/properties/baz": path.abs("/") + "#/properties/foo/properties/id" + "#/properties/baz": path.abs("/") + "#/properties/foo/properties/id", }); }); }); diff --git a/test/utils/helper.js b/test/utils/helper.js index 9f7c8985..6e0be22c 100644 --- a/test/utils/helper.js +++ b/test/utils/helper.js @@ -3,12 +3,13 @@ const $RefParser = require("../../lib"); const { host } = require("@jsdevtools/host-environment"); const { expect } = require("chai"); +const url = require("../../lib/util/url"); -const helper = module.exports = { +const helper = (module.exports = { /** * Throws an error if called. */ - shouldNotGetCalled () { + shouldNotGetCalled() { throw new Error("This function should not have gotten called."); }, @@ -20,9 +21,11 @@ const helper = module.exports = { * @param {...*} [params] - The expected resolved file paths and values * @returns {Function} */ - testResolve (filePath, params) { + testResolve(filePath, params) { let parsedSchema = arguments[2]; - let expectedFiles = [], messages = [], actualFiles; + let expectedFiles = [], + messages = [], + actualFiles; for (let i = 1; i < arguments.length; i += 2) { expectedFiles.push(arguments[i]); @@ -38,17 +41,21 @@ const helper = module.exports = { // Resolved file paths try { - expect((actualFiles = $refs.paths())).to.have.same.members(expectedFiles); + expect((actualFiles = $refs.paths())).to.have.same.members( + expectedFiles + ); if (host.node) { - expect((actualFiles = $refs.paths(["file"]))).to.have.same.members(expectedFiles); + expect((actualFiles = $refs.paths(["file"]))).to.have.same.members( + expectedFiles + ); expect($refs.paths("http")).to.be.an("array").with.lengthOf(0); - } - else { - expect((actualFiles = $refs.paths(["http"]))).to.have.same.members(expectedFiles); + } else { + expect((actualFiles = $refs.paths(["http"]))).to.have.same.members( + expectedFiles + ); expect($refs.paths("file")).to.be.an("array").with.lengthOf(0); } - } - catch (e) { + } catch (e) { console.log("Expected Files:", JSON.stringify(expectedFiles, null, 2)); console.log("Actual Files:", JSON.stringify(actualFiles, null, 2)); throw e; @@ -60,16 +67,43 @@ const helper = module.exports = { for (let [i, file] of expectedFiles.entries()) { let actual = helper.convertNodeBuffersToPOJOs(values[file]); let expected = messages[i]; + if (file !== filePath && !file.endsWith(filePath)) { + expected = this.addAbsolutePathToRefs(file, expected); + } expect(actual).to.deep.equal(expected, file); } }; }, + addAbsolutePathToRefs(filePath, expected) { + const clonedExpected = this.cloneDeep(expected); + if (typeof expected === "object") { + for (let [i, entry] of Object.entries(clonedExpected)) { + if (typeof entry === "object") { + if (typeof entry.$ref === "string") { + if (entry.$ref.startsWith("#")) { + clonedExpected[i].$ref = + url.toFileSystemPath(filePath) + entry.$ref; + } + continue; + } else { + clonedExpected[i] = this.addAbsolutePathToRefs(filePath, entry); + } + } + } + } + return clonedExpected; + }, + /** * Converts Buffer objects to POJOs, so they can be compared using Chai */ - convertNodeBuffersToPOJOs (value) { - if (value && (value._isBuffer || (value.constructor && value.constructor.name === "Buffer"))) { + convertNodeBuffersToPOJOs(value) { + if ( + value && + (value._isBuffer || + (value.constructor && value.constructor.name === "Buffer")) + ) { // Convert Buffers to POJOs for comparison value = value.toJSON(); @@ -77,20 +111,18 @@ const helper = module.exports = { // Node v0.10 serializes buffers differently value = { type: "Buffer", data: value }; } - } - else if (ArrayBuffer.isView(value)) { + } else if (ArrayBuffer.isView(value)) { value = { type: "Buffer", data: Array.from(value) }; } - return value; }, /** * Creates a deep clone of the given value. */ - cloneDeep (value) { + cloneDeep(value) { let clone = value; - if (value && typeof (value) === "object") { + if (value && typeof value === "object") { clone = value instanceof Array ? [] : {}; let keys = Object.keys(value); for (let i = 0; i < keys.length; i++) { @@ -99,4 +131,4 @@ const helper = module.exports = { } return clone; }, -}; +});