Skip to content

Commit

Permalink
fix(token manager): change project init errors
Browse files Browse the repository at this point in the history
  • Loading branch information
KutsenkoA committed May 28, 2020
1 parent f276112 commit 3adbd5a
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 26 deletions.
48 changes: 48 additions & 0 deletions e2e/stories/init/init.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as faker from "faker";
import { IAuthOptions, jexiaClient } from "../../../src/node";

describe("initialize client", () => {
it("should throw an error if project id is not provided", (done) => {
const options = {
key: faker.random.uuid(),
secret: faker.random.word(),
} as IAuthOptions;

jexiaClient().init(options)
.then(() => done.fail("successfully initialized"))
.catch(error => {
expect(error.message).toEqual("Please supply a valid Jexia project ID.");
done();
});
});

it("should throw an error if project is not found", (done) => {
const options = {
projectID: faker.random.uuid(),
key: faker.random.uuid(),
secret: faker.random.word(),
}

jexiaClient().init(options)
.then(() => done.fail("successfully initialized"))
.catch(error => {
expect(error.message).toEqual(`Authorization failed: project ${options.projectID} not found.`);
done();
});
});

it("should throw an error if credentials are incorrect", (done) => {
const options = {
projectID: process.env.E2E_PROJECT_ID as string,
key: faker.random.uuid(),
secret: faker.random.word(),
}

jexiaClient().init(options)
.then(() => done.fail("successfully initialized"))
.catch(error => {
expect(error.message).toEqual(`Authorization failed: incorrect key/secret`);
done();
});
});
});
12 changes: 11 additions & 1 deletion spec/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ResourceType } from "../src/api/core/resource";
import { FilesetInterface, FilesetMultipart, IFileStatus } from "../src/api/fileops/fileops.interfaces";
import { EventSubscriptionType, RealTimeEventMessage } from "../src/api/realtime/realTime.interfaces";
import { RequestExecuter } from "../src/internal/executer";
import { IHTTPResponse, IRequestOptions, RequestAdapter } from "../src/internal/requestAdapter";
import { IHTTPResponse, IRequestError, IRequestOptions, RequestAdapter } from "../src/internal/requestAdapter";

export class RequestAdapterMockFactory {
public genericSuccesfulExecution(): RequestAdapter {
Expand Down Expand Up @@ -186,6 +186,16 @@ export const mockFileEvent = (id: string, action: EventSubscriptionType): RealTi
data: [ { id }],
});

export const mockRequestError = ({
id = faker.random.uuid(),
request = {},
code = faker.random.number({ min: 100, max: 599 }),
status = faker.random.words(),
message = faker.lorem.sentence(),
}): IRequestError => ({
id, request, httpStatus: { code, status }, message,
});

export * from "./requestMethod";
export * from "./resource";
export * from "./queryActionType";
Expand Down
62 changes: 48 additions & 14 deletions src/api/core/tokenManager.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as faker from "faker";
import { Observable, of, throwError } from "rxjs";
import { createMockFor } from "../../../spec/testUtils";
import { createMockFor, mockRequestError } from "../../../spec/testUtils";
import { MESSAGE } from "../../config";
import { RequestAdapter, RequestMethod } from "../../internal/requestAdapter";
import { Logger } from "../logger/logger";
Expand Down Expand Up @@ -84,14 +84,27 @@ describe("TokenManager", () => {
});

describe("when authenticating", () => {
it("should throw an error if authentication failed", async () => {
it("should throw an error if project not found", async () => {
const { subject, validOptions } = createSubject({
requestAdapterReturnValue: throwError(new Error("Auth error")),
requestAdapterReturnValue: throwError({
httpStatus: { code: 404 },
}),
});
await subject.init(validOptions)
.catch((error) => expect(error.message).toEqual("Unable to get tokens: Auth error"));
.catch((error) => expect(error.message).toEqual(`Authorization failed: project ${validOptions.projectID} not found.`));
});

it("should throw an error if authorization failed", async () => {
const { subject, validOptions } = createSubject({
requestAdapterReturnValue: throwError({
httpStatus: { code: 500, status: "Server error" },
}),
});
await subject.init(validOptions)
.catch((error) => expect(error.message)
.toEqual(`Authorization failed: 500 Server error`));
})

it("should result promise of itself when authorization succeeded", async () => {
const { subject, validOptions } = createSubject();
const value = await subject.init(validOptions);
Expand All @@ -112,6 +125,27 @@ describe("TokenManager", () => {
await subject.init({ ...validOptions, auth });
expect((subject as any).startRefreshDigest).toHaveBeenCalledWith([auth]);
});

describe("get error message", () => {
it("should return not found error if received 404", () => {
const { subject, } = createSubject();
const projectID = faker.random.uuid();

subject.init({ projectID });

expect(subject.getErrorMessage(mockRequestError({ code: 404 })))
.toEqual(`Authorization failed: project ${projectID} not found.`);
});

it("should return code and status for any not 404 error", () => {
const { subject, } = createSubject();

const requestError = mockRequestError({ code: faker.helpers.randomize([401, 403, 407, 500]) });

expect(subject.getErrorMessage(requestError))
.toEqual(`Authorization failed: ${requestError.httpStatus.code} ${requestError.httpStatus.status}`);
});
});
});

describe("when get a token", () => {
Expand Down Expand Up @@ -311,30 +345,30 @@ describe("TokenManager", () => {
);
});

it("should reject promise if there is no token for specific auth", async () => {
it("should reject promise if there is no token for specific auth", () => {
const { subject } = createSubject();
(subject as any).storage.setTokens("testRefresh",
{ access_token: "access_token", refresh_token: "refresh_token" });
try {
await (subject as any).refresh(["randomAuth"]);
(subject as any).refresh(["randomAuth"]);
} catch (error) {
expect(error.message).toEqual("There is no refresh token for randomAuth");
}
});

it("should throw an error if request failed", async (done) => {
const { subject } = createSubject({
requestAdapterReturnValue: throwError(new Error("refresh error")),
});
(subject as any).storage.setTokens("testRefresh",
{ access_token: "access_token", refresh_token: "refresh_token" });
it("should throw an error if request failed", (done) => {
const { subject, tokens, requestAdapterMock } = createSubject();
(subject as any).storage.setTokens("testRefresh", tokens);

(requestAdapterMock.execute as jest.Mock).mockReturnValue(throwError({ httpStatus: { code: 500 }}));

(subject as any).refresh(["testRefresh"]).subscribe(
() => done("successfully refreshed the token"),
() => done.fail("successfully refreshed the token"),
(error: Error) => {
expect(error.message).toEqual("Unable to get tokens: refresh error");
expect(error.message).toEqual("Refreshing token failed");
done();
},
() => done.fail("completed without error"),
);
});
});
Expand Down
31 changes: 20 additions & 11 deletions src/api/core/tokenManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, InjectionToken } from "injection-js";
import { from, Observable } from "rxjs";
import { catchError, map, tap } from "rxjs/operators";
import { API, DELAY, MESSAGE } from "../../config";
import { RequestAdapter, RequestMethod } from "../../internal/requestAdapter";
import { IRequestError, RequestAdapter, RequestMethod } from "../../internal/requestAdapter";
import { Logger } from "../logger/logger";
import { TokenStorage } from "./componentStorage";

Expand Down Expand Up @@ -218,6 +218,9 @@ export class TokenManager {
tap((refreshedTokens: Tokens) =>
[auth, ...restAliases].forEach((alias) => this.storage.setTokens(alias, refreshedTokens)),
),
catchError(() => {
throw new Error("Refreshing token failed");
}),
);
}

Expand All @@ -231,16 +234,15 @@ export class TokenManager {
this.defers[auth] = new Promise((r) => resolve = r);

return this.requestAdapter.execute(url, {
body,
method: RequestMethod.POST,
}).pipe(
tap((refreshedTokens: Tokens) => resolve(refreshedTokens.access_token)),
catchError((err: Error) => {
delete this.defers[auth];
this.logger.error("tokenManager", err.message);
throw new Error(`Unable to get tokens: ${err.message}`);
}),
);
body,
method: RequestMethod.POST,
}).pipe(
tap((refreshedTokens: Tokens) => resolve(refreshedTokens.access_token)),
catchError((error: IRequestError) => {
delete this.defers[auth];
throw new Error(this.getErrorMessage(error));
}),
);
}

/**
Expand All @@ -266,4 +268,11 @@ export class TokenManager {
private get refreshUrl(): string {
return `${this.url}/${API.REFRESH}`;
}

public getErrorMessage({ httpStatus: { code, status } }: IRequestError): string {
if (code === 404) {
return `Authorization failed: project ${this.projectId} not found.`;
}
return `Authorization failed: ${code} ${status}`;
}
}

0 comments on commit 3adbd5a

Please sign in to comment.