Skip to content

Commit

Permalink
fix(TokenManager): fix refresh token time inconsistency when initiali…
Browse files Browse the repository at this point in the history
…zing
  • Loading branch information
willgm committed May 21, 2018
1 parent 7d44e2f commit 3c97eb4
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 40 deletions.
116 changes: 82 additions & 34 deletions src/api/core/tokenManager.spec.ts
Expand Up @@ -38,23 +38,21 @@ describe("Class: TokenManager", () => {
tm = new TokenManager(requestAdapterMockFactory().succesfulExecution({token: "validToken", refresh_token: "validRefreshToken"}));
});

it("should throw an error if application URL is not provided", (done) => {
(new TokenManager(requestAdapterMockFactory().genericSuccesfulExecution()))
it("should throw an error if application URL is not provided", async () => {
await (new TokenManager(requestAdapterMockFactory().genericSuccesfulExecution()))
.init({projectID: "", key: "validKey", secret: "validSecret"})
.then(() => done.fail("should throw app URL error"))
.then(() => { throw new Error("should have throw app URL error"); })
.catch((err: Error) => {
expect(err).toEqual(new Error("Please supply a valid Jexia project ID."));
done();
});
});

it("should throw an error if key or secret is not provided", (done) => {
(new TokenManager(requestAdapterMockFactory().genericSuccesfulExecution()))
it("should throw an error if key or secret is not provided", async () => {
await (new TokenManager(requestAdapterMockFactory().genericSuccesfulExecution()))
.init({projectID: validProjectID, key: "", secret: ""})
.then(() => done.fail("should throw credentials error"))
.then(() => { throw new Error("should have throw authentication error"); })
.catch((err: Error) => {
expect(err).toEqual(new Error("Please provide valid application credentials."));
done();
});
});

Expand All @@ -68,18 +66,57 @@ describe("Class: TokenManager", () => {
});
});

it("should have valid token and refresh token if authorization succeeded", (done) => {
tm.init({projectID: validProjectID, key: "validKey", secret: "validSecret"})
.then((output: TokenManager) => {
expect(output instanceof TokenManager).toBe(true);
return output["tokens"];
})
.then((tokens: IAuthToken) => {
expect(tokens.token).toBe("validToken");
expect(tokens.refreshToken).toBe("validRefreshToken");
done();
})
.catch((err: Error) => done.fail(`init should not have failed: ${err.message}`));
it("should try login when storage has no token", async () => {
spyOn(TokenStorage.getStorageAPI(), "isEmpty").and.returnValue(true);
spyOn(tm as any, "login").and.returnValue(Promise.resolve());
const opts = { ...validOpts(), authMethod: () => authMethodMock().authMethod };
await tm.init(opts);
expect(tm["login"]).toHaveBeenLastCalledWith(opts);
});

it("should fail initialization when login fails", async () => {
spyOn(TokenStorage.getStorageAPI(), "isEmpty").and.returnValue(true);
const loginError = "loginError";
spyOn(tm as any, "login").and.returnValue(Promise.reject(loginError));
const opts = { ...validOpts(), authMethod: () => authMethodMock().authMethod };
try {
await tm.init(opts);
throw new Error("init should have failed!");
} catch (error) {
expect(error).toBe(loginError);
}
});

it("should try refresh session when storage has no token", async () => {
spyOn(TokenStorage.getStorageAPI(), "isEmpty").and.returnValue(false);
spyOn(tm as any, "refresh").and.returnValue(Promise.resolve());
const opts = { ...validOpts(), authMethod: () => authMethodMock().authMethod };
await tm.init(opts);
expect(tm["refresh"]).toHaveBeenLastCalledWith(opts.projectID);
});

it("should fail initialization when refresh session fails", async () => {
spyOn(TokenStorage.getStorageAPI(), "isEmpty").and.returnValue(false);
const loginError = "loginError";
spyOn(tm as any, "refresh").and.returnValue(Promise.reject(loginError));
const opts = { ...validOpts(), authMethod: () => authMethodMock().authMethod };
try {
await tm.init(opts);
throw new Error("init should have failed!");
} catch (error) {
expect(error).toBe(loginError);
}
});

it("should result itself at then promise when authorization succeeded", async () => {
const result = await tm.init(validOpts());
expect(result).toBe(tm);
});

it("should have valid token and refresh token if authorization succeeded", async () => {
const tokens = await tm.init(validOpts()).then((out) => out["tokens"]);
expect(tokens.token).toBe("validToken");
expect(tokens.refreshToken).toBe("validRefreshToken");
});

it("should throw an error if the token is accessed before login", (done) => {
Expand Down Expand Up @@ -161,19 +198,31 @@ describe("Class: TokenManager", () => {

describe("when refreshing the token", () => {
it("should use the auth method adapter correct", (done) => {
const { authMethod, resultValue } = authMethodMock();
const { authMethod } = authMethodMock();
const opts = { ...validOpts(), authMethod: () => authMethod };
const tm = tokenManagerWithTokens();
spyOn(tm["storage"], "setTokens");
tm.init(opts)
.then((tokenManager) => {
const tokens = tokenManager["tokens"];
.then(({ tokens, requestAdapter }) => {
setTimeout(() => {
expect(authMethod.refresh).toHaveBeenCalledWith(
tokens,
tokenManager["requestAdapter"],
requestAdapter,
opts.projectID,
);
done();
}, opts.refreshInterval + 10);
})
.catch((err: Error) => done.fail(`init should not have failed: ${err.message}`));
});

it("should set the new tokens after a success refresh", (done) => {
const { authMethod, resultValue } = authMethodMock();
const opts = { ...validOpts(), authMethod: () => authMethod };
const tm = tokenManagerWithTokens();
tm.init(opts)
.then(({ tokens, requestAdapter }) => {
spyOn(tm["storage"], "setTokens");
setTimeout(() => {
expect(tm["storage"].setTokens).toHaveBeenCalledWith(resultValue);
done();
}, opts.refreshInterval + 10);
Expand All @@ -183,7 +232,7 @@ describe("Class: TokenManager", () => {

it("should throw an error on refresh token failure", (done) => {
tokenManagerWithTokens()
.init({projectID: validProjectID, key: "validKey", secret: "validSecret"})
.init(validOpts())
.then((tokenManager: TokenManager) => {
tokenManager["requestAdapter"] = requestAdapterMockFactory().failedExecution("refresh error.");
tokenManager["refresh"](validProjectID)
Expand All @@ -197,18 +246,17 @@ describe("Class: TokenManager", () => {
});

it("should have updated token after successful auto refresh", (done) => {
const { authMethod, resultValue } = authMethodMock();
const opts = validOpts();
tokenManagerWithTokens()
.init(validOpts())
.init({ ...opts, authMethod: () => authMethod })
.then((tokenManager: TokenManager) => {
spyOn(tokenManager["storage"], "setTokens");
tokenManager["requestAdapter"] = requestAdapterMockFactory().succesfulExecution({token: "updatedToken", refresh_token: "updatedRefreshToken"});
setTimeout(() => {
tokenManager.token
.then((token: string) => {
expect(token).toBe("updatedToken");
done();
})
.catch((err: Error) => done.fail(`refresh should not have failed: ${err.message}`));
}, 700);
expect(tokenManager["storage"].setTokens).toHaveBeenLastCalledWith(resultValue);
done();
}, opts.refreshInterval + 10);
})
.catch((err: Error) => done.fail(`init should not have failed: ${err.message}`));
});
Expand Down
12 changes: 6 additions & 6 deletions src/api/core/tokenManager.ts
Expand Up @@ -116,7 +116,7 @@ export class TokenManager {
return Promise.reject(new Error("Please supply a valid Jexia project ID."));
}

this.tokens = this.storage.isEmpty() ? this.login(opts) : this.storage.getTokens();
this.tokens = this.storage.isEmpty() ? this.login(opts) : this.refresh(opts.projectID);

/* make sure that tokens have been successfully received */
return this.tokens
Expand All @@ -127,7 +127,7 @@ export class TokenManager {
public terminate(): void {
this.storage.clear();
clearInterval(this.refreshInterval);
delete this.tokens;
this.tokens = null as any;
}

private refreshToken(opts: IAuthOptions) {
Expand All @@ -143,12 +143,12 @@ export class TokenManager {

private login(opts: IAuthOptions): Promise<IAuthToken> {
/* no need to wait for tokens */
return this.authMethod.login(opts, this.requestAdapter).
then((tokens: IAuthToken) => this.storage.setTokens(tokens));
return this.authMethod.login(opts, this.requestAdapter)
.then((tokens: IAuthToken) => this.storage.setTokens(tokens));
}

private refresh(projectID: string): Promise<IAuthToken> {
return this.authMethod.refresh(this.tokens, this.requestAdapter, projectID).
then((tokens) => this.storage.setTokens(tokens));
return this.tokens = this.authMethod.refresh(this.storage.getTokens(), this.requestAdapter, projectID)
.then((tokens) => this.storage.setTokens(tokens));
}
}

0 comments on commit 3c97eb4

Please sign in to comment.