matthsc/gigaset-elements-api

View on GitHub
src/requestHelper.spec.ts

Summary

Maintainability
D
1 day
Test Coverage
import { EndpointError, NetworkError } from "./errors";
import { RequestBase, url, urlParams } from "./requestHelper";
import { assert } from "chai";
import nock from "nock";

interface RequestHelperUrlObject {
  [key: string]: string | (() => void);
}

describe("request helper url objects", () => {
  describe("- url", () => {
    it("exists", () => {
      assert.exists(url);
      assert.isObject(url);
    });

    for (const prop of ["basestations", "status", "login", "auth", "events"]) {
      it("contains url for " + prop, () => {
        assert.property(url, prop);
        assert.exists((url as unknown as RequestHelperUrlObject)[prop]);
        assert.isNotEmpty((url as unknown as RequestHelperUrlObject)[prop]);
      });
    }

    it("is frozen", () => {
      assert.isFrozen(url);
    });

    it("cannot be changed", (done) => {
      const doesNotExistProp = "doesNotExist";
      const test = "test123";
      try {
        (url as unknown as RequestHelperUrlObject)[doesNotExistProp] = test;
        assert.fail();
      } catch {
        // this is expected, nothing to do
      }
      assert.notExists(
        (url as unknown as RequestHelperUrlObject)[doesNotExistProp],
      );

      const existProp = "auth";
      const before = url.auth;
      try {
        (url as unknown as RequestHelperUrlObject)[existProp] = test;
        assert.fail();
      } catch {
        // this is expected, nothing to do
      }
      assert.equal(url.auth, before);
      done();
    });
  });

  describe("- urlParams", () => {
    it("exists", () => {
      assert.exists(urlParams);
      assert.isObject(urlParams);
    });
    it("is frozen", () => {
      assert.isFrozen(urlParams);
    });
    it("its properties are frozen", () => {
      for (const key of Object.keys(urlParams) as Array<keyof typeof urlParams>)
        assert.isFrozen(urlParams[key]);
    });
  });
});

describe("requestBase.get", () => {
  const uri = "http://example.com/api/v0/get";
  const returnObject = Object.freeze({ foo: true, bar: 200, void: "diov" });
  let interceptor: nock.Interceptor;

  beforeEach(() => {
    interceptor = nock("http://example.com").get("/api/v0/get");
  });
  afterEach(() => {
    nock.cleanAll();
  });

  it("performs a http GET request", async () => {
    const scope = interceptor.reply(200, {});
    await new RequestBase().get(uri);
    assert.isTrue(scope.isDone());
  });

  it("returns the GET response as JSON object", async () => {
    const scope = interceptor.reply(200, returnObject);
    const result = await new RequestBase().get(uri);
    assert.deepEqual(result, returnObject);
    assert.isTrue(scope.isDone());
  });

  it("can handle empty responses", async () => {
    const scope = interceptor.reply(200);
    await new RequestBase().get(uri);
    assert.isTrue(scope.isDone());
  });

  it("throws NetworkError on unknown exceptions", async () => {
    const message = "Unknown network error";
    const scope = interceptor.replyWithError(message);
    try {
      await new RequestBase().get(uri);
      assert.fail("no error thrown");
    } catch (err: unknown) {
      assert.isTrue(scope.isDone());
      assert.instanceOf(err, NetworkError);
      assert.include((err as Error).message, message);
    }
  });

  it("throws EndpointError on API errors", async () => {
    const body = { message: "Unauthorized" };
    const scope = interceptor.reply(401, body);
    try {
      await new RequestBase().get(uri);
      assert.fail("no error thrown");
    } catch (err: unknown) {
      assert.isTrue(scope.isDone());
      assert.instanceOf(err, EndpointError);
      assert.equal((err as Error).message, JSON.stringify(body));
    }
  });

  // TODO: check that cookie from authorization is included in get request
});

describe("requestBase.post", () => {
  const uri = "http://example.com/api/v0/post";
  const returnObject = Object.freeze({ foo: true, bar: 200, void: "diov" });

  function setupInterceptor(body?: nock.RequestBodyMatcher) {
    return nock("http://example.com").post("/api/v0/post", body);
  }
  afterEach(() => {
    nock.cleanAll();
  });

  it("performs a http POST request", async () => {
    const scope = setupInterceptor().reply(200, {});
    await new RequestBase().post(uri);
    assert.isTrue(scope.isDone());
  });

  it("returns the POST response as JSON object", async () => {
    const scope = setupInterceptor().reply(200, returnObject);
    const result = await new RequestBase().post(uri);
    assert.deepEqual(result, returnObject);
    assert.isTrue(scope.isDone());
  });

  it("sends the body", async () => {
    nock.cleanAll();
    const scope = setupInterceptor(returnObject).reply(200);
    await new RequestBase().post(uri, { body: returnObject });
    assert.isTrue(scope.isDone());
  });

  it("sends a form", async () => {
    nock.cleanAll();
    const scope = setupInterceptor("alpha=1&beta=test").reply(200);
    await new RequestBase().post(uri, { form: { alpha: 1, beta: "test" } });
    assert.isTrue(scope.isDone());
  });

  it("can handle empty responses", async () => {
    const scope = setupInterceptor().reply(200);
    await new RequestBase().post(uri);
    assert.isTrue(scope.isDone());
  });

  it("throws NetworkError on unknown exceptions", async () => {
    const message = "Unknown network error";
    const scope = setupInterceptor().replyWithError(message);
    try {
      await new RequestBase().post(uri);
      assert.fail("no error thrown");
    } catch (err: unknown) {
      assert.isTrue(scope.isDone());
      assert.instanceOf(err, NetworkError);
      assert.include((err as Error).message, message);
    }
  });

  it("throws EndpointError on API errors", async () => {
    const body = { message: "Unauthorized" };
    const scope = setupInterceptor().reply(401, body);
    try {
      await new RequestBase().post(uri);
      assert.fail("no error thrown");
    } catch (err: unknown) {
      assert.isTrue(scope.isDone());
      assert.instanceOf(err, EndpointError);
      assert.equal((err as Error).message, JSON.stringify(body));
    }
  });

  // TODO: check that cookie from authorization is included in post request
});

describe("requestBase request logging", () => {
  const url = "http://example.com";
  let requestBase: RequestBase;
  const logged: string[] = [];

  beforeEach(() => {
    requestBase = new RequestBase((message) => {
      logged.push(message);
    });
  });
  afterEach(() => {
    logged.length = 0;
  });

  async function runTests(method: "get" | "post") {
    const reply1 = {};
    const reply2 = { alpha: 1, beta: "2" };
    const reply3 = { m: "NO" };
    const scope = nock(url)
      [method]("/1")
      .reply(200, reply1)
      [method]("/2")
      .reply(200, reply2)
      [method]("/3")
      .reply(403, reply3);

    await requestBase[method](url + "/" + 1);
    await requestBase[method](url + "/" + 2);
    try {
      await requestBase[method](url + "/" + 3);
      assert(false);
    } catch {
      assert(true);
    }
    assert(scope.isDone());
    assert.lengthOf(logged, 6);

    let i = 0;
    assert.equal(logged[i++], method + " " + url + "/1");
    assert.equal(logged[i++], "200 " + JSON.stringify(reply1));
    assert.equal(logged[i++], method + " " + url + "/2");
    assert.equal(logged[i++], "200 " + JSON.stringify(reply2));
    assert.equal(logged[i++], method + " " + url + "/3");
    assert.equal(logged[i++], "403 " + JSON.stringify(reply3));
  }

  for (const method of ["get", "post"] as Array<"get" | "post">) {
    it("for " + method + " requests", async () => {
      await runTests(method);
    });
  }
});