Skip to content
This repository has been archived by the owner on Oct 30, 2022. It is now read-only.

Commit

Permalink
Service state validation (#63)
Browse files Browse the repository at this point in the history
* Retrieves operations in Service for state validation and update

* Verifies given state against schema

* Gets matching operations for every state update

* Add logging, replace internal throw with top-level

* Moves state request validation to validator.ts

* Update validator to include schema properties

* Fixes getAtLevel to handle array elements
  • Loading branch information
idantene committed Jul 3, 2019
1 parent 3124875 commit f48034a
Show file tree
Hide file tree
Showing 13 changed files with 710 additions and 153 deletions.
86 changes: 56 additions & 30 deletions packages/unmock-core/src/__tests__/service.util.test.ts
@@ -1,4 +1,5 @@
import XRegExp from "xregexp";
import { Parameter } from "../service/interfaces";
import {
buildPathRegexStringFromParameters,
getAtLevel,
Expand Down Expand Up @@ -73,34 +74,37 @@ describe("Tests getPathParametersFromPath", () => {
});

describe("Tests getPathParametersFromSchema", () => {
const baseSchema = (params: Parameter[]) => ({
get: { parameters: params, responses: {} },
});
it("Tests a path without parameters", () => {
const schema = { "/pets": { get: { parameters: [{ in: "path" }] } } };
const schema = {
"/pets": { ...baseSchema([{ in: "path", name: "foo" }]) },
};
expect(getPathParametersFromSchema(schema, "/pets").length).toBe(0);
});

it("Tests a dynamic path with existing parameters", () => {
const schema = {
"/pets/{petId}": {
get: {
parameters: [
{ in: "path", name: "petId" },
{ in: "query", name: "foo" },
],
},
...baseSchema([
{ in: "path", name: "petId" },
{ in: "query", name: "foo" },
]),
},
};
expect(getPathParametersFromSchema(schema, "/pets/{petId}").length).toBe(1);
});

it("Tests a path without schema", () => {
const schema = {
"/pets/{petId}": { get: { parameters: [{ in: "path" }] } },
"/pets/{petId}": { ...baseSchema([{ in: "path", name: "foo" }]) },
};
expect(getPathParametersFromSchema(schema, "/pets/").length).toBe(0);
});

it("Tests a dynamic path without parameters", () => {
const schema = { "/pets/{petId}": { get: { parameters: []] } } };
const schema = { "/pets/{petId}": { ...baseSchema([]) } };
expect(() => getPathParametersFromSchema(schema, "/pets/{petId}")).toThrow(
"no description for path parameters",
);
Expand All @@ -109,41 +113,63 @@ describe("Tests getPathParametersFromSchema", () => {

describe("Tests buildPathRegexStringFromParameters", () => {
const emptySchema: any[] = [];
const schemaWithMatchingParams = [
{
0: { in: "path", name: "petId" },
1: { in: "query", name: "foo" },
},
const schemaWithMatchingParams: Parameter[] = [
{ in: "path", name: "petId" },
{ in: "query", name: "foo" },
];
const schemaWithMissingParams = [
{
0: { in: "path", name: "id" },
1: { in: "query", name: "foo" },
},
const schemaWithMissingParams: Parameter[] = [
{ in: "path", name: "id" },
{ in: "query", name: "foo" },
];

it("Tests path", () => {
// Empty schema - nothing to do
expect(buildPathRegexStringFromParameters("/pets", emptySchema, [])).toBe("/pets");
expect(buildPathRegexStringFromParameters("/pets", emptySchema, [])).toBe(
"/pets",
);
// Empty schema - nothing to do (even if parameters are given)
expect(buildPathRegexStringFromParameters("/pets", emptySchema, ["petId"])).toBe("/pets");
expect(
buildPathRegexStringFromParameters("/pets", emptySchema, ["petId"]),
).toBe("/pets");
// Parameter from path matches parameter from schema, but no replacement takes place
expect(buildPathRegexStringFromParameters("/pets", schemaWithMatchingParams, ["petId"])).toBe("/pets");
// Expected to fail as we still have a path parameter that was unresolved
expect(() => buildPathRegexStringFromParameters("/pets", schemaWithMissingParams, ["petId"]))
.toThrow("following path parameters have not been described");
expect(
buildPathRegexStringFromParameters("/pets", schemaWithMatchingParams, [
"petId",
]),
).toBe("/pets");
// // Expected to fail as we still have a path parameter that was unresolved
expect(() =>
buildPathRegexStringFromParameters("/pets", schemaWithMissingParams, [
"petId",
]),
).toThrow("following path parameters have not been described");
});

it("Tests dynamic path", () => {
// Empty schema - nothing to do
expect(buildPathRegexStringFromParameters("/pets/{petId}", emptySchema, [])).toBe("/pets/{petId}");
expect(buildPathRegexStringFromParameters("/pets/{petId}", emptySchema, ["petId"])).toBe("/pets/{petId}");
expect(
buildPathRegexStringFromParameters("/pets/{petId}", emptySchema, []),
).toBe("/pets/{petId}");
expect(
buildPathRegexStringFromParameters("/pets/{petId}", emptySchema, [
"petId",
]),
).toBe("/pets/{petId}");
// Replacement should happen
const newPath = buildPathRegexStringFromParameters("/pets/{petId}", schemaWithMatchingParams, ["petId"]);
const newPath = buildPathRegexStringFromParameters(
"/pets/{petId}",
schemaWithMatchingParams,
["petId"],
);
expect(newPath).toBe("/pets/(?<petId>[^/]+)");
expect(XRegExp(newPath).test("/pets/2")).toBeTruthy();
// Expected to fail as we still have a path parameter that was unresolved
expect(() => buildPathRegexStringFromParameters("/pets/{petId}", schemaWithMissingParams, ["petId"]))
.toThrow("following path parameters have not been described");
expect(() =>
buildPathRegexStringFromParameters(
"/pets/{petId}",
schemaWithMissingParams,
["petId"],
),
).toThrow("following path parameters have not been described");
});
});
59 changes: 41 additions & 18 deletions packages/unmock-core/src/__tests__/serviceStore.test.ts
@@ -1,64 +1,82 @@
import { OpenAPIObject } from "loas3/dist/src/generated/full";
import { stateStoreFactory } from "../service";
import { Service } from "../service/service";

const schemaBase: OpenAPIObject = {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "Swagger Petstore",
license: { name: "MIT" },
},
paths: {},
};

describe("Fluent API and Service instantiation tests", () => {
// define some service populators that match IOASMappingGenerator type
const PetStoreWithoutPaths = [new Service({ schema: {}, name: "petstore" })];
const PetStoreWithEmptyPaths = [
new Service({ schema: { paths: {} }, name: "petstore" }),
new Service({ schema: schemaBase, name: "petstore" }),
];
const PetStoreWithPseudoPaths = [
const PetStoreWithEmptyResponses = [
new Service({
name: "petstore",
schema: { paths: { "/pets": { get: {} } } },
schema: { ...schemaBase, paths: { "/pets": { get: { responses: {} } } } },
}),
];
const PetStoreWithPseudoResponses = [
new Service({
name: "petstore",
schema: {
...schemaBase,
paths: {
"/pets": {
get: { responses: { 200: { description: "Mock response" } } },
},
},
},
}),
];

test("Store without paths", () => {
const store = stateStoreFactory(PetStoreWithoutPaths);
expect(store.noservice).toThrow("Can't find specification");
expect(store.petstore).toThrow("has no defined paths");
});

test("Store with empty paths", () => {
const store = stateStoreFactory(PetStoreWithEmptyPaths);
expect(store.noservice).toThrow("Can't find specification");
expect(store.petstore).toThrow("has no defined paths");
expect(store.petstore.get).toThrow("has no defined paths");
});

test("Store with non-empty paths with non-matching method", () => {
const store = stateStoreFactory(PetStoreWithPseudoPaths);
const store = stateStoreFactory(PetStoreWithEmptyResponses);
expect(store.petstore.post).toThrow("Can't find any endpoints with method");
});

test("Store with basic call", () => {
const store = stateStoreFactory(PetStoreWithPseudoPaths);
const store = stateStoreFactory(PetStoreWithPseudoResponses);
store.petstore(); // Should pass
});

test("Store with REST method call", () => {
const store = stateStoreFactory(PetStoreWithPseudoPaths);
const store = stateStoreFactory(PetStoreWithPseudoResponses);
store.petstore.get(); // Should pass
});

test("Chaining multiple states without REST methods", () => {
const store = stateStoreFactory(PetStoreWithPseudoPaths);
const store = stateStoreFactory(PetStoreWithPseudoResponses);
store
.petstore()
.petstore()
.petstore();
});

test("Chaining multiple states with REST methods", () => {
const store = stateStoreFactory(PetStoreWithPseudoPaths);
const store = stateStoreFactory(PetStoreWithPseudoResponses);
store.petstore
.get()
.petstore.get()
.petstore();
});

test("Chaining multiple methods for a service", () => {
const store = stateStoreFactory(PetStoreWithPseudoPaths);
const store = stateStoreFactory(PetStoreWithPseudoResponses);
store.petstore
.get()
.get()
Expand All @@ -68,13 +86,13 @@ describe("Fluent API and Service instantiation tests", () => {
});

test("Specifying endpoint without rest method", () => {
const store = stateStoreFactory(PetStoreWithPseudoPaths);
const store = stateStoreFactory(PetStoreWithPseudoResponses);
store.petstore("/pets"); // should pass
expect(() => store.petstore("/pet")).toThrow("Can't find endpoint");
});

test("Specifying endpoint with rest method", () => {
const store = stateStoreFactory(PetStoreWithPseudoPaths);
const store = stateStoreFactory(PetStoreWithPseudoResponses);
store.petstore.get("/pets"); // should pass
expect(() => store.petstore.post("/pets")).toThrow("Can't find response");
expect(() => store.petstore.get("/pet")).toThrow("Can't find endpoint");
Expand All @@ -92,6 +110,10 @@ describe("Test paths matching on serviceStore", () => {
description: "The id of the pet to retrieve",
schema: { type: "string" },
},
{
name: "test",
in: "path",
},
],
};
const DynamicPathsService = (
Expand All @@ -102,6 +124,7 @@ describe("Test paths matching on serviceStore", () => {
return [
new Service({
schema: {
...schemaBase,
paths: {
[path]: {
get: {
Expand Down

0 comments on commit f48034a

Please sign in to comment.