Repository: Redocly/openapi-cli
Pull request #153 (2020-06-25 11:53:15)

Add Info and Servers tests, WIP Paths tests


Patch:
diff --git a/src/types/oas3.ts b/src/types/oas3.ts
index c40ee63f..4ba891f0 100644
--- a/src/types/oas3.ts
+++ b/src/types/oas3.ts
@@ -122,7 +122,7 @@ const License: NodeType = {
 
 const PathMap: NodeType = {
   properties: {},
-  additionalProperties: (_value: any, key: string) => (key.startsWith('/') ? 'PathItem' : null),
+  additionalProperties: (_value: any, key: string) => (key.startsWith('/') ? 'PathItem' : undefined),
 };
 
 const PathItem: NodeType = {


Test patch:
diff --git a/src/rules/oas3/__tests__/schema/info.test.ts b/src/rules/oas3/__tests__/schema/info.test.ts
new file mode 100644
index 00000000..c3c92717
--- /dev/null
+++ b/src/rules/oas3/__tests__/schema/info.test.ts
@@ -0,0 +1,391 @@
+import { outdent } from 'outdent';
+import { validateDoc } from './utils';
+
+describe('OpenAPI Schema', () => {
+  it('should report if the title of the API is empty ', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title:
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info/title",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  it('should report if in the description field is not string.', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition.
+        version: '1.0'
+        description:
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info/description",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  it('should report if in the termsOfService field is not string', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+        termsOfService:
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info/termsOfService",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  it('should not report if the Contact Object is valid', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+        termsOfService: http://example.com/terms/
+        contact:
+          name: API Support
+          url: http://www.example.com/support
+          email: support@example.com
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should report if in the Contact Object in URL field is not string', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+        termsOfService: http://example.com/terms/
+        contact:
+          url:
+          email: support@example.com
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info/contact/url",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  it('should report if in the Contact Object in email field is not string', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+        termsOfService: http://example.com/terms/
+        contact:
+          url: http://example.com/contact/
+          email:
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info/contact/email",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  it('should not report if the License Object is valid', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+        license:
+          name: Apache 2.0
+          url: https://www.apache.org/licenses/LICENSE-2.0.html
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should report if the License Object missing field Name', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+        license:
+          url: https://www.apache.org/licenses/LICENSE-2.0.html
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info/license",
+          "message": "The field 'name' must be present on this level.",
+        },
+      ]
+    `);
+  });
+
+  it('should report if in the URL field of the License Object is not string', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+        license:
+          name: Apache 2.0
+          url:
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info/license/url",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  it('should not report if the URL field of the License Object is not provided', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+        license:
+          name: Apache 2.0
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should report if the Version field is not provided', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition.
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info",
+          "message": "The field 'version' must be present on this level.",
+        },
+      ]
+    `);
+  });
+
+  it('should report if in the Version field is not string', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        version:
+        title: Example OpenAPI 3 definition.
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/info/version",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+});
diff --git a/src/rules/oas3/__tests__/schema/operation.ts b/src/rules/oas3/__tests__/schema/operation.ts
new file mode 100644
index 00000000..1f624ffe
--- /dev/null
+++ b/src/rules/oas3/__tests__/schema/operation.ts
@@ -0,0 +1,253 @@
+import { outdent } from "outdent";
+import { validateDoc } from "./utils";
+
+it('should not report if summary field is valid', async () => {
+  const source = outdent`
+    openapi: 3.0.2
+    info:
+      title: Test
+      version: '1.0'
+
+    servers:
+      - url: http://google.com
+
+    paths:
+      '/ping':
+        summary: test
+        get:
+          responses:
+            '200':
+              description: example description
+  `;
+
+  expect(
+    await validateDoc(source, {
+      spec: 'error',
+    }),
+  ).toMatchInlineSnapshot(`Array []`);
+});
+
+it('should report if summary field is not string ', async () => {
+  const source = outdent`
+    openapi: 3.0.2
+    info:
+      title: Test
+      version: '1.0'
+
+    servers:
+      - url: http://google.com
+
+    paths:
+      '/ping':
+        summary:
+        get:
+          responses:
+            '200':
+              description: example description
+  `;
+
+  expect(
+    await validateDoc(source, {
+      spec: 'error',
+    }),
+  ).toMatchInlineSnapshot(`
+    Array [
+      Object {
+        "location": "#/paths/~1ping/summary",
+        "message": "Expected type 'string' but got 'null'",
+      },
+    ]
+  `);
+});
+
+it('should not report if description field is valid', async () => {
+  const source = outdent`
+    openapi: 3.0.2
+    info:
+      title: Test
+      version: '1.0'
+
+    servers:
+      - url: http://google.com
+
+    paths:
+      '/ping':
+        description: test
+        get:
+          responses:
+            '200':
+              description: example description
+  `;
+
+  expect(
+    await validateDoc(source, {
+      spec: 'error',
+    }),
+  ).toMatchInlineSnapshot(`Array []`);
+});
+
+it('should report if description field is not string', async () => {
+  const source = outdent`
+    openapi: 3.0.2
+    info:
+      title: Test
+      version: '1.0'
+
+    servers:
+      - url: http://google.com
+
+    paths:
+      '/ping':
+        description:
+        get:
+          responses:
+            '200':
+              description: example description
+  `;
+
+  expect(
+    await validateDoc(source, {
+      spec: 'error',
+    }),
+  ).toMatchInlineSnapshot(`
+    Array [
+      Object {
+        "location": "#/paths/~1ping/description",
+        "message": "Expected type 'string' but got 'null'",
+      },
+    ]
+  `);
+});
+
+it('should not report of a valid GET operation object', async () => {
+  const source = outdent`
+    openapi: 3.0.2
+    info:
+      title: Test
+      version: '1.0'
+
+    servers:
+      - url: http://google.com
+
+    paths:
+      '/ping':
+        get:
+          responses:
+            '200':
+              description: example description
+  `;
+
+  expect(
+    await validateDoc(source, {
+      spec: 'error',
+    }),
+  ).toMatchInlineSnapshot(`Array []`);
+});
+
+it('should not report of a valid PUT operation object', async () => {
+  const source = outdent`
+    openapi: 3.0.2
+    info:
+      title: Test
+      version: '1.0'
+
+    servers:
+      - url: http://google.com
+
+    paths:
+      '/ping':
+        put:
+          tags:
+            - pet
+          summary: Update an existing pet
+          description: ''
+          operationId: updatePet
+          responses:
+            '400':
+              description: Invalid ID supplied
+  `;
+
+  expect(
+    await validateDoc(source, {
+      spec: 'error',
+    }),
+  ).toMatchInlineSnapshot(`Array []`);
+});
+
+it('should not report of a valid Post operation object', async () => {
+  const source = outdent`
+    openapi: 3.0.2
+    info:
+      title: Test
+      version: '1.0'
+
+    servers:
+      - url: http://google.com
+
+    paths:
+      '/ping':
+        post:
+          tags:
+            - pet
+          summary: uploads an image
+          description: ''
+          operationId: uploadFile
+          parameters:
+            - name: petId
+              in: path
+              description: ID of pet to update
+              required: true
+              schema:
+                type: integer
+                format: int64
+          responses:
+            '200':
+              description: successful operation
+  `;
+
+  expect(
+    await validateDoc(source, {
+      spec: 'error',
+    }),
+  ).toMatchInlineSnapshot(`Array []`);
+});
+
+it('should not report of a valid delete operation object', async () => {
+  const source = outdent`
+    openapi: 3.0.2
+    info:
+      title: Test
+      version: '1.0'
+
+    servers:
+      - url: http://google.com
+
+    paths:
+      '/ping':
+        delete:
+          tags:
+            - store
+          summary: Delete purchase order by ID
+          description: >-
+            For valid response try integer IDs with value < 1000. Anything above
+            1000 or nonintegers will generate API errors
+          operationId: deleteOrder
+          parameters:
+            - name: orderId
+              in: path
+              description: ID of the order that needs to be deleted
+              required: true
+              schema:
+                type: string
+                minimum: 1
+          responses:
+            '400':
+              description: Invalid ID supplied
+  `;
+
+  expect(
+    await validateDoc(source, {
+      spec: 'error',
+    }),
+  ).toMatchInlineSnapshot(`Array []`);
+});
\ No newline at end of file
diff --git a/src/rules/oas3/__tests__/schema/paths.test.ts b/src/rules/oas3/__tests__/schema/paths.test.ts
new file mode 100644
index 00000000..4dd80f18
--- /dev/null
+++ b/src/rules/oas3/__tests__/schema/paths.test.ts
@@ -0,0 +1,278 @@
+import { outdent } from 'outdent';
+import { validateDoc } from './utils';
+
+describe('OpenAPI Schema', () => {
+  it('should not report if Path object is valid ', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not report if Path object is empty ', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should report if Path object is not present ', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/",
+          "message": "The field 'paths' must be present on this level.",
+        },
+      ]
+    `);
+  });
+
+  it('should not report if Path object is empty ', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  //Check: no error
+  it('should report if the field name is not begin with a forward slash (/) ', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        'ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/paths/ping",
+          "message": "Property \`ping\` is not expected here",
+        },
+      ]
+    `);
+  });
+
+  // TODO: should be a separate rule
+  it.skip('should report if paths are considered identical and invalid', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/pets/{petId}':
+          get:
+            responses:
+              '200':
+                description: example description
+        '/pets/{name}':
+           get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not report valid matching URLs', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/pets/{petId}':
+          get:
+            responses:
+              '200':
+                description: example description
+        '/pets/mine':
+           get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not report in case of ambiguous matching ', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/{entity}/me':
+          get:
+            responses:
+              '200':
+                description: example description
+        '/books/{id}':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not report if Path Item is empty ', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        '/ping':
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not report of a valid Parameter Object', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Test
+        version: '1.0'
+
+      servers:
+        - url: http://google.com
+
+      paths:
+        /pet:
+          parameters:
+            - name: Accept-Language
+              in: header
+              description: "test"
+              example: en-US
+              required: false
+              schema:
+                type: string
+                default: en-AU
+          post:
+            tags:
+              - pet
+            summary: Add a new pet to the store
+            description: Add new pet to the store inventory.
+            operationId: addPet
+            responses:
+              '405':
+                description: Invalid input
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+});
diff --git a/src/rules/oas3/__tests__/schema/servers.test.ts b/src/rules/oas3/__tests__/schema/servers.test.ts
index 88041cf3..52287119 100644
--- a/src/rules/oas3/__tests__/schema/servers.test.ts
+++ b/src/rules/oas3/__tests__/schema/servers.test.ts
@@ -2,7 +2,7 @@ import { outdent } from 'outdent';
 import { validateDoc } from './utils';
 
 describe('OpenAPI Schema', () => {
-  it('should not report on valid servers', async () => {
+  it('should not report on valid Server Object', async () => {
     const source = outdent`
       openapi: 3.0.2
       info:
@@ -10,7 +10,8 @@ describe('OpenAPI Schema', () => {
         version: '1.0'
 
       servers:
-        - url: https://google.com
+        - url: https://server.com/v1
+          description: Development server
 
       paths:
         '/ping':
@@ -22,7 +23,466 @@ describe('OpenAPI Schema', () => {
 
     expect(
       await validateDoc(source, {
-        schema: 'error',
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should report on empty server URL', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url:
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/servers/0/url",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  it('should report on missing server URL', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+       - description: Development server
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/servers/0",
+          "message": "The field 'url' must be present on this level.",
+        },
+      ]
+    `);
+  });
+
+  it('should report on empty description field', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://server.com/v1
+          description:
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/servers/0/description",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  it('should not report if description field is missing', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://example.com
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should report if fields type in servers are not array', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        description: Development server
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/servers",
+          "message": "Expected type 'Server_List (array)' but got 'object'",
+        },
+      ]
+    `);
+  });
+
+  it('should not report on multiple servers', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://development.server.com/v1
+          description: Development server
+        - url: https://staging.server.com/v1
+          description: Staging server
+        - url: https://api.server.com/v1
+          description: Production server
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not report if variables are used for a server configuration', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://{username}.server.com:{port}/{basePath}
+          variables:
+            username:
+              default: demo
+            port:
+              enum:
+                - '8443'
+                - '443'
+              default: '8443'
+            basePath:
+              default: v2
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should report if array in enum is empty', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://{username}.server.com:{port}/{basePath}
+          variables:
+            username:
+              default: demo
+            port:
+              enum:
+
+              default: '8443'
+            basePath:
+              default: v2
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/servers/0/variables/port/enum",
+          "message": "Expected type 'array' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  // TODO: should be a separate rule
+  it.skip('should report if some variable is not defined', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://{username}.server.com:{port}/{basePath}
+          variables:
+            username:
+              default: demo
+            port:
+              enum:
+                - '8443'
+                - '443'
+              default: '8443'
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should report if default value is not provided in variables', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://{username}.server.com:{port}/{basePath}
+          variables:
+            username:
+            port:
+              enum:
+                - '8443'
+                - '443'
+            basePath:
+              default: v2
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/servers/0/variables/port",
+          "message": "The field 'default' must be present on this level.",
+        },
+      ]
+    `);
+  });
+
+  it('should report if default value in variables is empty', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://{username}.server.com:{port}/{basePath}
+          variables:
+            username:
+              default:
+            port:
+              enum:
+                - '8443'
+                - '443'
+              default: '8443'
+            basePath:
+              default: v2
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "location": "#/servers/0/variables/username/default",
+          "message": "Expected type 'string' but got 'null'",
+        },
+      ]
+    `);
+  });
+
+  //Check: No error
+  it('should report if description in variable is not a string', async () => {
+    const source = outdent`
+      openapi: 3.0.2
+      info:
+        title: Example OpenAPI 3 definition. Valid.
+        version: '1.0'
+
+      servers:
+        - url: https://{username}.server.com:{port}/{basePath}
+          variables:
+            username:
+              default: demo
+              description:
+            port:
+              enum:
+                - '8443'
+                - '443'
+              default: '8443'
+            basePath:
+              default: v2
+
+      paths:
+        '/ping':
+          get:
+            responses:
+              '200':
+                description: example description
+    `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not report if servers property is not provided', async () => {
+    const source = outdent`
+        openapi: 3.0.2
+        info:
+          title: Example OpenAPI 3 definition. Valid.
+          version: '1.0'
+
+        paths:
+          '/ping':
+            get:
+              responses:
+                '200':
+                  description: example description
+      `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
+      }),
+    ).toMatchInlineSnapshot(`Array []`);
+  });
+
+  it('should not report if servers property is an empty array', async () => {
+    const source = outdent`
+        openapi: 3.0.2
+        info:
+          title: Example OpenAPI 3 definition. Valid.
+          version: '1.0'
+
+        servers:
+
+        paths:
+          '/ping':
+            get:
+              responses:
+                '200':
+                  description: example description
+      `;
+
+    expect(
+      await validateDoc(source, {
+        spec: 'error',
       }),
     ).toMatchInlineSnapshot(`Array []`);
   });