Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions functions/conditionalRequestHeaderMatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright 2022 Cisco Systems, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Checks targetVal has corresponding conditional request and response header.
* @param {string} targetVal The string to lint
*/
module.exports = function (targetVal) {
if (typeof targetVal !== 'object') {
return;
}

let successResponse = {};

for (const code in targetVal.responses) {
if (code.startsWith('2')) {
successResponse = targetVal.responses[code];
break;
}
}

if (successResponse.headers == null) {
return [];
}

const resHeaders = [];

for (const header in successResponse.headers) {
if (Object.values(pairs).includes(header)) {
resHeaders.push(header);
}
}

if (targetVal.parameters == null) {
return [];
}

for (const param of targetVal.parameters) {
if (param.in === 'header' && pairs[param.name] !== undefined) {
if (!resHeaders.includes(pairs[param.name])) {
return [
{
message: `${
pairs[param.name]
} is missing in response header for conditional request header ${
param.name
}: (https://developer.cisco.com/docs/api-insights/#!api-guidelines-analyzer)`,
},
];
}
}
}
};

const pairs = {
'If-Unmodified-Since': 'Last-Modified',
'If-Modified-Since': 'Last-Modified',
'If-Range': 'Last-Modified',
'If-Match': 'Etag',
};
84 changes: 84 additions & 0 deletions functions/conditionalRequestHeaderMatch.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright 2022 Cisco Systems, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
const conditionalRequestHeaderMatch = require('./conditionalRequestHeaderMatch');

describe('conditionalRequestHeaderMatch', () => {
const targetVal = {
parameters: [
{
in: 'header',
name: 'If-Match',
schema: {
type: 'string',
},
},
],
responses: {
200: {
description: 'OK',
headers: {
Etag: {
schema: {
type: 'string',
},
},
},
},
},
};

test('should pass when header have all the values', () => {
const res = conditionalRequestHeaderMatch(targetVal);

expect(res).toBeUndefined();
});

test('should fail if pair response header is missing for conditional request', () => {
const targetVal = {
parameters: [
{
in: 'header',
name: 'If-Match',
schema: {
type: 'string',
},
},
],
responses: {
200: {
description: 'OK',
headers: {
'Last-Modified': {
schema: {
type: 'string',
},
},
},
},
},
};
const res = conditionalRequestHeaderMatch(targetVal);

expect(res).toEqual([
{
message:
'Etag is missing in response header for conditional request header If-Match: (https://developer.cisco.com/docs/api-insights/#!api-guidelines-analyzer)',
},
]);
});
});
94 changes: 94 additions & 0 deletions test/conditional-request-etag-or-last-modified.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import fsPromises from 'fs/promises';
import path from 'path';
import CiscoLinter from '../src/CiscoLinter';
import { prepLinter } from '../src/util/testUtils';
const ruleName = 'conditional-request-etag-or-last-modified';
/**
* Copyright 2022 Cisco Systems, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
const resPath = path.join(__dirname, `resources/${ ruleName }`);

describe(ruleName, () => {
let spectral;

beforeAll(async () => {
spectral = new CiscoLinter(undefined);
await prepLinter(spectral, 'cisco-without-oas', ruleName);
});

test('should throw an error if conditional request does not contain etag or last-modified', async () => {
const spec = await fsPromises.readFile(`${ resPath }/negative.yml`);
const res = await spectral.run(spec.toString());

expect(res).toEqual([
{
code: ruleName,
message: "Conditional requests are designed with 'Etag' or 'Last-Modified' headers (https://developer.cisco.com/docs/api-insights/#!api-guidelines-analyzer)",
path: [
'paths',
'/test/{testId}',
'get',
],
range: {
start: {
line: 14,
character: 8,
},
end: {
line: 32,
character: 32,
},
},
severity: 1,
},
{
code: ruleName,
message: "Conditional requests are designed with 'Etag' or 'Last-Modified' headers (https://developer.cisco.com/docs/api-insights/#!api-guidelines-analyzer)",
path: [
'paths',
'/anotherTest',
'get',
],
range: {
start: {
line: 34,
character: 8,
},
end: {
line: 50,
character: 28,
},
},
severity: 1,
},
]);
});

test('should pass if conditional request contains etag or last-modified header for openapi v3', async () => {
const spec = await fsPromises.readFile(`${ resPath }/positive.yml`);
const res = await spectral.run(spec.toString());

expect(res).toEqual([]);
});

test('should pass if conditional request contains etag or last-modified header for openapi v2', async () => {
const spec = await fsPromises.readFile(`${ resPath }/positive-oas2.yml`);
const res = await spectral.run(spec.toString());

expect(res).toEqual([]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
swagger: '2.0'
info:
contact:
name: Scott Hardin
description: This is a sample API.
title: Sample API
version: '1.0'
host: api.example.com
basePath: /
schemes:
- http
paths:
/anotherTest:
get:
parameters:
- in: header
name: If-Range
type: string
responses:
'200':
description: No Content
headers:
Etag:
type: string
tags:
- Sample
description: get some more test data.
operationId: getAnotherTestData
/test/{testId}:
get:
parameters:
- in: header
name: If-Range
type: string
responses:
'200':
description: Created
headers:
SomeHeader:
type: string
'404':
description: Not Found
tags:
- Sample
description: get some test data.
operationId: getTestData
tags:
- description: This is a sample tag.
name: Sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
openapi: '3.0.3'
info:
title: Sample API
description: This is a sample API.
version: '1.0'
contact:
name: Scott Hardin
servers:
- url: http://api.example.com
tags:
- name: Sample
description: This is a sample tag.
paths:
/test/{testId}:
get:
description: get some test data.
operationId: getTestData
tags:
- Sample
parameters:
- in: header
name: If-Range
schema:
type: string
responses:
'200':
description: Created
headers:
SomeHeader:
schema:
type: string
'404':
description: Not Found
/anotherTest:
get:
description: get some more test data.
operationId: getAnotherTestData
tags:
- Sample
parameters:
- in: header
name: If-Range
schema:
type: string
responses:
'200':
description: No Content
headers:
Etag:
schema:
type: string
Loading