Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import testRule from './__helpers__/testRule';
import { DiagnosticSeverity } from '@stoplight/types';

const READ_ONLY_SCHEMA_ERROR_MESSAGE =
'The Operation resource must be read-only. All properties of the GET response schema must be marked as readOnly: true.';

const readOnlyGet = {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
properties: {
id: { readOnly: true },
status: { readOnly: true },
},
},
},
},
},
},
};

const nonReadOnlyGet = {
responses: {
200: {
content: {
'application/vnd.atlas.2024-08-05+json': {
schema: {
properties: {
id: { readOnly: true },
status: { type: 'string' },
},
},
},
},
},
},
};

testRule('xgen-IPA-132-operation-must-be-a-read-only-resource', [
{
name: 'valid read-only Operations endpoints',
document: {
paths: {
'/api/atlas/v2/resourceName/operations': {
get: readOnlyGet,
},
'/api/atlas/v2/resourceName/operations/{operationId}': {
get: readOnlyGet,
},
'/api/atlas/v2/resourceName/{pathParam}/operations': {
get: readOnlyGet,
},
'/api/atlas/v2/resourceName/{pathParam}/operations/{operationId}': {
get: readOnlyGet,
},
},
},
errors: [],
},
{
name: 'paths that are not Operations endpoints are ignored',
document: {
paths: {
'/api/atlas/v2/resourceName': {
post: {},
},
'/api/atlas/v2/resourceName/{pathParam}': {
put: {},
patch: {},
delete: {},
},
},
},
errors: [],
},
{
name: 'invalid Operations endpoints with methods other than get',
document: {
paths: {
'/api/atlas/v2/resourceName/operations': {
get: readOnlyGet,
post: {},
},
'/api/atlas/v2/resourceName/operations/{operationId}': {
Comment thread
yelizhenden-mdb marked this conversation as resolved.
get: readOnlyGet,
put: {},
patch: {},
delete: {},
head: {},
},
},
},
errors: [
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: 'Operations endpoints are read-only and do not allow the post method.',
path: ['paths', '/api/atlas/v2/resourceName/operations', 'post'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: 'Operations endpoints are read-only and do not allow the put method.',
path: ['paths', '/api/atlas/v2/resourceName/operations/{operationId}', 'put'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: 'Operations endpoints are read-only and do not allow the patch method.',
path: ['paths', '/api/atlas/v2/resourceName/operations/{operationId}', 'patch'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: 'Operations endpoints are read-only and do not allow the delete method.',
path: ['paths', '/api/atlas/v2/resourceName/operations/{operationId}', 'delete'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: 'Operations endpoints are read-only and do not allow the head method.',
path: ['paths', '/api/atlas/v2/resourceName/operations/{operationId}', 'head'],
severity: DiagnosticSeverity.Warning,
},
],
},
{
name: 'invalid Operations endpoint with a declared but empty method',
document: {
paths: {
'/api/atlas/v2/resourceName/operations': {
get: readOnlyGet,
post: null,
},
},
},
errors: [
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: 'Operations endpoints are read-only and do not allow the post method.',
path: ['paths', '/api/atlas/v2/resourceName/operations', 'post'],
severity: DiagnosticSeverity.Warning,
},
],
},
{
name: 'invalid Operation resources with properties that are not readOnly, checked per path item',
document: {
paths: {
'/api/atlas/v2/resourceName/operations': {
get: nonReadOnlyGet,
},
'/api/atlas/v2/resourceName/operations/{operationId}': {
get: nonReadOnlyGet,
},
},
},
errors: [
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: READ_ONLY_SCHEMA_ERROR_MESSAGE,
path: ['paths', '/api/atlas/v2/resourceName/operations', 'get'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: READ_ONLY_SCHEMA_ERROR_MESSAGE,
path: ['paths', '/api/atlas/v2/resourceName/operations/{operationId}', 'get'],
severity: DiagnosticSeverity.Warning,
},
],
},
{
name: 'invalid Operations endpoints with exceptions',
document: {
paths: {
'/api/atlas/v2/resourceName/operations': {
get: readOnlyGet,
post: {},
'x-xgen-IPA-exception': {
'xgen-IPA-132-operation-must-be-a-read-only-resource': 'reason',
},
},
},
},
errors: [],
},
{
name: 'read-only Operations endpoints do not need an exception',
document: {
paths: {
'/api/atlas/v2/resourceName/operations': {
get: readOnlyGet,
'x-xgen-IPA-exception': {
'xgen-IPA-132-operation-must-be-a-read-only-resource': 'reason',
},
},
},
},
errors: [
{
code: 'xgen-IPA-132-operation-must-be-a-read-only-resource',
message: 'This component adopts the rule and does not need an exception. Please remove the exception.',
path: [
'paths',
'/api/atlas/v2/resourceName/operations',
'x-xgen-IPA-exception',
'xgen-IPA-132-operation-must-be-a-read-only-resource',
],
severity: DiagnosticSeverity.Warning,
},
],
},
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import testRule from './__helpers__/testRule';
import { DiagnosticSeverity } from '@stoplight/types';

const ERROR_MESSAGE =
'Operations endpoints must be leaf resources. An `operations` segment may only be followed by a single operation identifier path parameter.';

testRule('xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource', [
{
name: 'valid leaf Operations endpoints',
document: {
paths: {
'/api/atlas/v2/resourceName/operations': {},
'/api/atlas/v2/resourceName/operations/{operationId}': {},
'/api/atlas/v2/resourceName/{pathParam}/operations': {},
'/api/atlas/v2/resourceName/{pathParam}/operations/{operationId}': {},
// Root-level Operations endpoints are leaves, their nesting is covered by
// xgen-IPA-132-operations-endpoint-must-not-be-global
'/api/atlas/v2/operations': {},
},
},
errors: [],
},
{
name: 'paths without an operations segment are ignored',
document: {
paths: {
'/api/atlas/v2/resourceName': {},
'/api/atlas/v2/resourceName/{pathParam}': {},
'/api/atlas/v2/resourceName/{pathParam}/childResource': {},
},
},
errors: [],
},
{
name: 'invalid paths nested below Operations endpoints',
document: {
paths: {
'/api/atlas/v2/resourceName/operations/subresource': {},
'/api/atlas/v2/resourceName/operations/{operationId}/subresource': {},
'/api/atlas/v2/resourceName/operations/{operationId}/{anotherId}': {},
// Nesting below the first operations segment is a violation even when the path ends in
// another well-formed operations suffix
'/api/atlas/v2/resourceName/operations/{operationId}/operations': {},
},
},
errors: [
{
code: 'xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource',
message: ERROR_MESSAGE,
path: ['paths', '/api/atlas/v2/resourceName/operations/subresource'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource',
message: ERROR_MESSAGE,
path: ['paths', '/api/atlas/v2/resourceName/operations/{operationId}/subresource'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource',
message: ERROR_MESSAGE,
path: ['paths', '/api/atlas/v2/resourceName/operations/{operationId}/{anotherId}'],
severity: DiagnosticSeverity.Warning,
},
{
code: 'xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource',
message: ERROR_MESSAGE,
path: ['paths', '/api/atlas/v2/resourceName/operations/{operationId}/operations'],
severity: DiagnosticSeverity.Warning,
},
],
},
{
name: 'invalid paths with exceptions',
document: {
paths: {
'/api/atlas/v2/resourceName/operations/{operationId}/subresource': {
'x-xgen-IPA-exception': {
'xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource': 'reason',
},
},
},
},
errors: [],
},
{
name: 'leaf Operations endpoints do not need an exception',
document: {
paths: {
'/api/atlas/v2/resourceName/operations/{operationId}': {
'x-xgen-IPA-exception': {
'xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource': 'reason',
},
},
},
},
errors: [
{
code: 'xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource',
message: 'This component adopts the rule and does not need an exception. Please remove the exception.',
path: [
'paths',
'/api/atlas/v2/resourceName/operations/{operationId}',
'x-xgen-IPA-exception',
'xgen-IPA-132-operations-endpoint-must-be-a-leaf-resource',
],
severity: DiagnosticSeverity.Warning,
},
],
},
]);
Loading
Loading