diff --git a/src/formats/asyncapi.ts b/src/formats/asyncapi.ts index f50852e0fd..de8e18846e 100644 --- a/src/formats/asyncapi.ts +++ b/src/formats/asyncapi.ts @@ -1,6 +1,10 @@ -import { bearsAStringPropertyNamed } from './bearsAStringPropertyNamed'; +import { isObject } from 'lodash'; type MaybeAsyncApi2 = Partial<{ asyncapi: unknown }>; +export const bearsAStringPropertyNamed = (document: unknown, propertyName: string) => { + return isObject(document) && propertyName in document && typeof document[propertyName] === 'string'; +}; + export const isAsyncApiv2 = (document: unknown) => bearsAStringPropertyNamed(document, 'asyncapi') && parseFloat(String((document as MaybeAsyncApi2).asyncapi)) === 2; diff --git a/src/formats/bearsAStringPropertyNamed.ts b/src/formats/bearsAStringPropertyNamed.ts deleted file mode 100644 index cf778a53c1..0000000000 --- a/src/formats/bearsAStringPropertyNamed.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { isObject } from 'lodash'; - -export const bearsAStringPropertyNamed = (document: unknown, propertyName: string) => { - return isObject(document) && propertyName in document && typeof document[propertyName] === 'string'; -}; diff --git a/src/__tests__/__fixtures__/aas/streetlight.yaml b/src/rulesets/aas/__tests__/__fixtures__/streetlights.yaml similarity index 100% rename from src/__tests__/__fixtures__/aas/streetlight.yaml rename to src/rulesets/aas/__tests__/__fixtures__/streetlights.yaml diff --git a/src/rulesets/aas/__tests__/streetlights.jest.test.ts b/src/rulesets/aas/__tests__/streetlights.jest.test.ts new file mode 100644 index 0000000000..f4cd5a903c --- /dev/null +++ b/src/rulesets/aas/__tests__/streetlights.jest.test.ts @@ -0,0 +1,142 @@ +import * as path from '@stoplight/path'; +import { DiagnosticSeverity } from '@stoplight/types'; +import * as nock from 'nock'; + +import { Document } from '../../../document'; +import { isAsyncApiv2 } from '../../../formats'; +import { readParsable } from '../../../fs/reader'; +import { unreferencedReusableObject } from '../../../functions/unreferencedReusableObject'; +import { RuleType, Spectral } from '../../../index'; +import * as Parsers from '../../../parsers'; +import { httpAndFileResolver } from '../../../resolvers/http-and-file'; +import { rules } from '../index.json'; + +describe('unusedComponentsSchema - Http and fs remote references', () => { + const s = new Spectral({ resolver: httpAndFileResolver }); + s.registerFormat('aas2', isAsyncApiv2); + + describe('reports unreferenced components schemas', () => { + test('when analyzing an in-memory document', async () => { + nock('https://oas3.library.com') + .get('/defs.json') + .reply( + 200, + JSON.stringify({ + components: { + schemas: { + ExternalHttp: { + type: 'number', + }, + }, + }, + }), + ); + + const remoteFsRefeferencePath = path.join( + __dirname, + '../../__tests__/__fixtures__/unusedComponentsSchema.definition.json#/components/schemas/ExternalFs', + ); + + const doc = `{ + "openapi": "3.0.0", + "x-hook": { + "$ref": "#/components/schemas/Hooked" + }, + "x-also-hook": { + "$ref": "#/components/schemas/Hooked" + }, + "paths": { + "/path": { + "post": { + "parameters": [ + { + "$ref": "#/components/schemas/HookedAsWell" + }, + { + "$ref": "${remoteFsRefeferencePath}" + }, + { + "$ref": "https://oas3.library.com/defs.json#/components/schemas/ExternalHttp" + } + ] + } + } + }, + "components": { + "schemas": { + "Hooked": { + "type": "object" + }, + "HookedAsWell": { + "name": "value", + "in": "query", + "type": "number" + }, + "Unhooked": { + "type": "object" + } + } + } + }`; + + const results = await s.run(new Document(doc, Parsers.Json)); + + expect(results).toEqual([ + { + code: 'oas3-unused-components-schema', + message: 'Potentially unused components schema has been detected.', + path: ['components', 'schemas', 'Unhooked'], + range: { + end: { + character: 11, + line: 37, + }, + start: { + character: 22, + line: 35, + }, + }, + severity: DiagnosticSeverity.Warning, + }, + ]); + + nock.cleanAll(); + }); + + test('when analyzing a directly self-referencing document from the filesystem', async () => { + const fixturePath = path.join(__dirname, '../../__tests__/__fixtures__/unusedComponentsSchema.remoteLocal.json'); + + const spec = await readParsable(fixturePath, { encoding: 'utf8' }); + const results = await s.run(new Document(spec, Parsers.Json, fixturePath)); + + expect(results).toEqual([]); + }); + + test('when analyzing an indirectly self-referencing document from the filesystem', async () => { + const fixturePath = path.join(__dirname, '../../__tests__/__fixtures__/unusedComponentsSchema.indirect.1.json'); + + const spec = await readParsable(fixturePath, { encoding: 'utf8' }); + const results = await s.run(new Document(spec, Parsers.Json, fixturePath)); + + expect(results).toEqual([ + { + code: 'oas3-unused-components-schema', + message: 'Potentially unused components schema has been detected.', + path: ['components', 'schemas', 'Unhooked'], + range: { + end: { + character: 7, + line: 12, + }, + start: { + character: 18, + line: 10, + }, + }, + severity: DiagnosticSeverity.Warning, + source: expect.stringMatching('/__tests__/__fixtures__/unusedComponentsSchema.indirect.1.json$'), + }, + ]); + }); + }); +});