link-stack/packages/hapi-nextauth/src/index.spec.ts

364 lines
9 KiB
TypeScript
Raw Normal View History

import * as Hapi from "@hapi/hapi";
import HapiBasic from "@hapi/basic";
import NextAuthPlugin from ".";
describe("plugin option validation", () => {
let server;
beforeEach(async () => {
server = new Hapi.Server();
});
it("should throw when options contain no next auth adapter", async () => {
expect(server.register(NextAuthPlugin)).rejects.toThrow();
});
});
describe("plugin runtime", () => {
let server;
const user = { id: "abc", email: "abc@abc.abc" };
const session = {
id: "zyx",
userId: "abc",
expires: Date.now(),
sessionToken: "foo",
accessToken: "bar",
};
const start = async (mock) => {
await server.register(HapiBasic);
await server.register({
plugin: NextAuthPlugin,
options: {
nextAuthAdapterFactory: () => mock,
},
});
await server.start();
return server;
};
beforeEach(async () => {
server = new Hapi.Server({ port: 0 });
});
afterEach(async () => {
await server.stop();
});
it("createUser", async () => {
const createUser = jest.fn(() => user);
const profile = { email: "abc@abc.abc" };
await start({ createUser });
const { statusCode, result } = await server.inject({
method: "post",
url: "/api/nextauth/createUser",
payload: profile,
});
expect(statusCode).toBe(200);
expect(createUser).toHaveBeenCalledWith(profile);
expect(result).toStrictEqual(user);
});
it("createUser fails with invalid payload", async () => {
const createUser = jest.fn(() => user);
const profile = { name: "name" };
await start({ createUser });
const { statusCode } = await server.inject({
method: "post",
url: "/api/nextauth/createUser",
payload: profile,
});
expect(statusCode).toBe(400);
});
it("getUser", async () => {
const getUser = jest.fn(() => user);
await start({ getUser });
const { statusCode, result } = await server.inject({
method: "get",
url: "/api/nextauth/getUser/abc",
});
expect(statusCode).toBe(200);
expect(getUser).toHaveBeenCalledWith("abc");
expect(result).toBe(user);
});
it("getUserByEmail", async () => {
const getUserByEmail = jest.fn(() => user);
await start({ getUserByEmail });
const { statusCode, result } = await server.inject({
method: "get",
url: "/api/nextauth/getUserByEmail/abc@abc.abc",
});
expect(statusCode).toBe(200);
expect(getUserByEmail).toHaveBeenCalledWith("abc@abc.abc");
expect(result).toBe(user);
});
it("getUserByEmail fails with invalid email", async () => {
const getUserByEmail = jest.fn(() => user);
await start({ getUserByEmail });
const { statusCode } = await server.inject({
method: "get",
url: "/api/nextauth/getUserByEmail/notanemail@foo",
});
expect(statusCode).toBe(400);
});
it("getUserByProviderAccountId", async () => {
const getUserByProviderAccountId = jest.fn(() => user);
await start({ getUserByProviderAccountId });
const { statusCode, result } = await server.inject({
method: "get",
url: "/api/nextauth/getUserByProviderAccountId/foo/bar",
});
expect(statusCode).toBe(200);
expect(getUserByProviderAccountId).toHaveBeenCalledWith("foo", "bar");
expect(result).toBe(user);
});
it("updateUser", async () => {
const updateUser = jest.fn(() => user);
await start({ updateUser });
const { statusCode, result } = await server.inject({
method: "put",
url: "/api/nextauth/updateUser",
payload: user,
});
expect(statusCode).toBe(200);
expect(updateUser).toHaveBeenCalledWith(user);
expect(result).toStrictEqual(user);
});
it("updateUser fails with invalid payload", async () => {
const updateUser = jest.fn(() => user);
await start({ updateUser });
const { statusCode } = await server.inject({
method: "put",
url: "/api/nextauth/updateUser",
payload: {
// id not specified
email: "abc@abc.abc",
},
});
expect(statusCode).toBe(400);
});
it("linkUser", async () => {
const linkAccount = jest.fn(() => undefined);
const args = {
userId: "abc",
providerId: "foo",
providerType: "something",
providerAccountId: "bar",
refreshToken: "refreshToken",
accessToken: "accessToken",
accessTokenExpires: 10,
};
await start({ linkAccount });
const { statusCode } = await server.inject({
method: "put",
url: "/api/nextauth/linkAccount",
payload: args,
});
expect(statusCode).toBe(204);
expect(linkAccount.mock.calls.length).toBe(1);
});
it("createSession", async () => {
const createSession = jest.fn(() => session);
await start({ createSession });
const { statusCode, result } = await server.inject({
method: "post",
url: "/api/nextauth/createSession",
payload: user,
});
expect(statusCode).toBe(200);
expect(createSession).toHaveBeenCalledWith(user);
expect(result).toStrictEqual(session);
});
it("getSession", async () => {
const getSession = jest.fn(() => session);
await start({ getSession });
const { statusCode, result } = await server.inject({
method: "get",
url: "/api/nextauth/getSession/xyz",
});
expect(statusCode).toBe(200);
expect(getSession).toHaveBeenCalledWith("xyz");
expect(result).toBe(session);
});
it("updateSession", async () => {
const updateSession = jest.fn(() => session);
await start({ updateSession });
const { statusCode, result } = await server.inject({
method: "put",
url: "/api/nextauth/updateSession",
payload: session,
});
expect(statusCode).toBe(200);
expect(updateSession).toHaveBeenCalledWith(
{
...session,
expires: new Date(session.expires),
},
false
);
expect(result).toStrictEqual(session);
});
it("updateSession - force", async () => {
const updateSession = jest.fn(() => session);
await start({ updateSession });
const { statusCode, result } = await server.inject({
method: "put",
url: "/api/nextauth/updateSession?force=true",
payload: session,
});
expect(statusCode).toBe(200);
expect(updateSession).toHaveBeenCalledWith(
{
...session,
expires: new Date(session.expires),
},
true
);
expect(result).toStrictEqual(session);
});
it("deleteSession", async () => {
const deleteSession = jest.fn(() => undefined);
await start({ deleteSession });
const { statusCode } = await server.inject({
method: "delete",
url: "/api/nextauth/deleteSession/xyz",
});
expect(statusCode).toBe(204);
expect(deleteSession).toHaveBeenCalledWith("xyz");
});
});
describe("plugin authentication", () => {
const user = { id: "abc", email: "abc@abc.abc" };
const sharedSecret = "secret";
let server;
const start = async (mock) => {
await server.register(HapiBasic);
await server.register({
plugin: NextAuthPlugin,
options: {
nextAuthAdapterFactory: () => mock,
sharedSecret,
},
});
await server.start();
return server;
};
const basicHeader = (username, password) =>
"Basic " +
Buffer.from(username + ":" + password, "utf8").toString("base64");
beforeEach(async () => {
server = new Hapi.Server({ port: 0 });
});
afterEach(async () => {
await server.stop();
});
it("getUser - no auth header fails", async () => {
const getUser = jest.fn(() => user);
await start({ getUser });
const { statusCode } = await server.inject({
method: "get",
url: "/api/nextauth/getUser/abc",
});
expect(statusCode).toBe(401);
expect(getUser).toHaveBeenCalledTimes(0);
});
it("getUser - with auth header suceeds", async () => {
const getUser = jest.fn(() => user);
await start({ getUser });
const { statusCode, result } = await server.inject({
method: "get",
url: "/api/nextauth/getUser/abc",
headers: {
authorization: basicHeader(sharedSecret, ""),
},
});
expect(statusCode).toBe(200);
expect(getUser).toHaveBeenCalledWith("abc");
expect(result).toBe(user);
});
it("getUser - with invalid credentials fails", async () => {
const getUser = jest.fn(() => user);
await start({ getUser });
const { statusCode } = await server.inject({
method: "get",
url: "/api/nextauth/getUser/abc",
headers: {
authorization: basicHeader("wrong secret", ""),
},
});
expect(statusCode).toBe(401);
expect(getUser).toHaveBeenCalledTimes(0);
});
it("getUser - with secret in password field fails", async () => {
const getUser = jest.fn(() => user);
await start({ getUser });
const { statusCode } = await server.inject({
method: "get",
url: "/api/nextauth/getUser/abc",
headers: {
authorization: basicHeader("", "sharedSecret"),
},
});
expect(statusCode).toBe(401);
expect(getUser).toHaveBeenCalledTimes(0);
});
});