2024-03-10 16:12:50 +00:00
|
|
|
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
|
2024-03-01 19:50:11 +00:00
|
|
|
import { createId } from "@paralleldrive/cuid2";
|
|
|
|
import { expect, test, type BrowserContext, type Page } from "@playwright/test";
|
2024-03-10 16:12:50 +00:00
|
|
|
import "dotenv/config";
|
|
|
|
import { eq } from "drizzle-orm";
|
|
|
|
import { drizzle } from "drizzle-orm/postgres-js";
|
2024-03-01 19:50:11 +00:00
|
|
|
import { Lucia, type Cookie } from "lucia";
|
|
|
|
import createClient from "openapi-fetch";
|
2024-03-10 16:12:50 +00:00
|
|
|
import postgres from "postgres";
|
2024-03-01 19:50:11 +00:00
|
|
|
import * as schema from "~/drizzle/schema";
|
2024-03-10 16:12:50 +00:00
|
|
|
import type * as discord from "~/types/discord";
|
|
|
|
import type * as liljudd from "~/types/liljudd";
|
2024-03-01 19:50:11 +00:00
|
|
|
|
2024-03-10 16:12:50 +00:00
|
|
|
const unencoded = `${process.env.DISCORD_CLIENT_ID}:${process.env.DISCORD_CLIENT_SECRET}`;
|
|
|
|
const encoded = btoa(unencoded);
|
2024-03-01 19:50:11 +00:00
|
|
|
|
|
|
|
const queryClient = postgres(process.env.DATABASE_URL!);
|
|
|
|
const db = drizzle(queryClient, {
|
|
|
|
schema,
|
|
|
|
});
|
|
|
|
|
|
|
|
const adapter = new DrizzlePostgreSQLAdapter(db, schema.sessions, schema.users);
|
|
|
|
export const lucia = new Lucia(adapter, {
|
|
|
|
getUserAttributes: (attributes) => attributes,
|
|
|
|
});
|
|
|
|
|
|
|
|
let context: BrowserContext;
|
|
|
|
let page: Page;
|
|
|
|
|
|
|
|
let sessionCookie: Cookie | undefined;
|
|
|
|
|
2024-03-10 16:12:50 +00:00
|
|
|
let userId = createId();
|
|
|
|
let guildId: bigint;
|
|
|
|
|
2024-03-01 19:50:11 +00:00
|
|
|
test.describe.serial("User auth process", () => {
|
2024-03-10 16:12:50 +00:00
|
|
|
test.beforeAll(() => {
|
|
|
|
expect(
|
|
|
|
[
|
|
|
|
"DISCORD_CLIENT_ID",
|
|
|
|
"DISCORD_CLIENT_SECRET",
|
|
|
|
"DATABASE_URL",
|
|
|
|
"DISCORD_BOT_TOKEN",
|
|
|
|
].filter((e) => typeof process.env[e] === "undefined").length,
|
|
|
|
{ message: "Please specify all env vars." },
|
|
|
|
).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
2024-03-01 19:50:11 +00:00
|
|
|
test.beforeAll(async ({ browser }) => {
|
|
|
|
context = await browser.newContext();
|
|
|
|
page = await context.newPage();
|
|
|
|
});
|
|
|
|
|
|
|
|
test.beforeEach(async () => {
|
|
|
|
if (!sessionCookie) return;
|
|
|
|
|
|
|
|
const sameSiteProps = {
|
|
|
|
lax: "Lax",
|
|
|
|
strict: "Strict",
|
|
|
|
none: "None",
|
|
|
|
} as const;
|
|
|
|
const expires = sessionCookie.attributes.expires
|
|
|
|
? sessionCookie.attributes.expires.getTime() / 1000
|
|
|
|
: undefined;
|
|
|
|
const sameSite = sessionCookie.attributes.sameSite
|
|
|
|
? sameSiteProps[sessionCookie.attributes.sameSite]
|
|
|
|
: undefined;
|
|
|
|
await context.addCookies([
|
|
|
|
{
|
|
|
|
name: sessionCookie.name,
|
|
|
|
value: sessionCookie.value,
|
|
|
|
...sessionCookie.attributes,
|
|
|
|
sameSite,
|
|
|
|
expires,
|
|
|
|
secure: false,
|
|
|
|
domain: "localhost",
|
|
|
|
path: "/",
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
2024-03-10 16:12:50 +00:00
|
|
|
test.afterAll("Delete DB entries", async () => {
|
|
|
|
await db.delete(schema.users).where(eq(schema.users.id, userId)).execute();
|
|
|
|
await db
|
|
|
|
.delete(schema.guilds)
|
|
|
|
.where(eq(schema.guilds.id, guildId))
|
|
|
|
.execute();
|
|
|
|
});
|
|
|
|
|
2024-03-01 19:50:11 +00:00
|
|
|
test.afterAll(async () => {
|
|
|
|
await context.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
test("Landing page", async () => {
|
|
|
|
await page.goto("/");
|
|
|
|
await page.waitForLoadState("load");
|
|
|
|
|
|
|
|
expect(await page.screenshot()).toMatchSnapshot("landing_page.png");
|
|
|
|
});
|
|
|
|
|
|
|
|
test("Unauthorized Access Redirect Test", async () => {
|
|
|
|
await page.goto("/config");
|
|
|
|
await page.waitForURL("/");
|
|
|
|
});
|
|
|
|
|
2024-03-10 16:12:50 +00:00
|
|
|
test("Generate auth session for further tests", async ({ browser }) => {
|
|
|
|
const { GET } = createClient<discord.paths>({
|
2024-03-01 19:50:11 +00:00
|
|
|
baseUrl: "https://discord.com/api/v10",
|
|
|
|
});
|
|
|
|
const discordUserResponse = await GET("/users/@me", {
|
|
|
|
headers: {
|
|
|
|
Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}`,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
if (discordUserResponse.error) throw discordUserResponse.error;
|
|
|
|
const discordUser = discordUserResponse.data;
|
2024-03-10 16:12:50 +00:00
|
|
|
|
|
|
|
const browserName = browser.browserType().name() as
|
|
|
|
| "chromium"
|
|
|
|
| "webkit"
|
|
|
|
| "firefox";
|
|
|
|
|
|
|
|
userId = discordUser.id + userId.slice(discordUser.id.length);
|
|
|
|
userId = userId.slice(0, -browserName.length) + browserName;
|
|
|
|
|
|
|
|
enum BrowserIds {
|
|
|
|
chromium,
|
|
|
|
webkit,
|
|
|
|
firefox,
|
|
|
|
}
|
|
|
|
guildId = BigInt(discordUser.id) ^ BigInt(BrowserIds[browserName]);
|
|
|
|
|
2024-03-01 19:50:11 +00:00
|
|
|
await db.insert(schema.users).values({
|
|
|
|
id: userId,
|
|
|
|
discord_id: discordUser.id,
|
|
|
|
name: discordUser.global_name,
|
|
|
|
image: discordUser.avatar,
|
|
|
|
});
|
|
|
|
const session = await lucia.createSession(
|
|
|
|
userId,
|
|
|
|
{},
|
|
|
|
{ sessionId: createId() },
|
|
|
|
);
|
|
|
|
sessionCookie = lucia.createSessionCookie(session.id);
|
|
|
|
await db
|
|
|
|
.insert(schema.discordTokens)
|
|
|
|
.values({
|
|
|
|
userId,
|
|
|
|
accessToken: "tokens.accessToken",
|
|
|
|
expiresAt: sessionCookie.attributes.expires ?? new Date(),
|
|
|
|
refreshToken: "tokens.refreshToken",
|
|
|
|
})
|
|
|
|
.returning()
|
|
|
|
.execute();
|
|
|
|
});
|
|
|
|
|
|
|
|
test("Landing page when logged in", async () => {
|
|
|
|
await page.goto("/");
|
|
|
|
await page.waitForLoadState("load");
|
|
|
|
|
|
|
|
expect(await page.screenshot()).toMatchSnapshot(
|
|
|
|
"landing_page_logged_in.png",
|
|
|
|
);
|
|
|
|
});
|
2024-03-10 16:12:50 +00:00
|
|
|
|
|
|
|
test("Test Api", async () => {
|
|
|
|
const { GET, POST, PUT } = createClient<liljudd.paths>({
|
|
|
|
baseUrl: "http://localhost:3000/",
|
|
|
|
});
|
|
|
|
|
|
|
|
const createConfigResponse = await POST("/api/{guildId}/config", {
|
|
|
|
params: {
|
|
|
|
path: {
|
|
|
|
guildId: guildId.toString(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
headers: {
|
|
|
|
Authorization: `Basic ${encoded}`,
|
|
|
|
Origin: "http://localhost:3000",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (createConfigResponse.error)
|
|
|
|
throw new Error(createConfigResponse.error.error);
|
|
|
|
|
|
|
|
let getConfigResponse = await GET("/api/{guildId}/config", {
|
|
|
|
params: {
|
|
|
|
path: {
|
|
|
|
guildId: guildId.toString(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
headers: {
|
|
|
|
Authorization: `Basic ${encoded}`,
|
|
|
|
Origin: "http://localhost:3000",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (getConfigResponse.error) throw new Error(getConfigResponse.error.error);
|
|
|
|
|
|
|
|
switch (getConfigResponse.data?.checksum) {
|
|
|
|
case "209a644c31a5ef123c432c2885d231a2e0efc4de": // chromium
|
|
|
|
case "aead21e132a94ab897ec28e0f0c337a66207bad3": // webkit
|
|
|
|
case "c3e2ff2ce5a8936234552125a54c2fe1ce1a35da": // firefox
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(
|
|
|
|
"Before guild GET checksum didn't matched known ones: " +
|
|
|
|
getConfigResponse.data?.checksum,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const putTimePlanningResponse = await PUT("/api/{guildId}/timePlanning", {
|
|
|
|
body: {
|
|
|
|
enabled: true,
|
|
|
|
channelId: "1234567890123456789",
|
|
|
|
rolesEnabled: true,
|
|
|
|
isAvailableRoleId: "1234567890123456789",
|
|
|
|
wantsToBeNotifieRoledId: "1234567890123456789",
|
|
|
|
messageIds: {
|
|
|
|
"0": "1234567890123456789",
|
|
|
|
"1": "1234567890123456789",
|
|
|
|
"2": "1234567890123456789",
|
|
|
|
"3": "1234567890123456789",
|
|
|
|
"4": "1234567890123456789",
|
|
|
|
"5": "1234567890123456789",
|
|
|
|
"6": "1234567890123456789",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
params: {
|
|
|
|
path: {
|
|
|
|
guildId: guildId.toString(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
headers: {
|
|
|
|
Authorization: `Basic ${encoded}`,
|
|
|
|
Origin: "http://localhost:3000",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (putTimePlanningResponse.error)
|
|
|
|
throw new Error(putTimePlanningResponse.error.error);
|
|
|
|
|
|
|
|
getConfigResponse = await GET("/api/{guildId}/config", {
|
|
|
|
params: {
|
|
|
|
path: {
|
|
|
|
guildId: guildId.toString(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
headers: {
|
|
|
|
Authorization: `Basic ${encoded}`,
|
|
|
|
Origin: "http://localhost:3000",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (getConfigResponse.error) throw new Error(getConfigResponse.error.error);
|
|
|
|
|
|
|
|
switch (getConfigResponse.data?.checksum) {
|
|
|
|
case "681c8324b21096255d942bb78bd6655da90d352e": // chromium
|
|
|
|
case "a2fb3601b7d0949b1ceada3b3ac0ba408c6159bb": // webkit
|
|
|
|
case "bf20daba95e8f3ddd17cc64e8a7ba184b68ad37b": // firefox
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Error(
|
|
|
|
"After guild GET checksum didn't matched known ones: " +
|
|
|
|
getConfigResponse.data?.checksum,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
2024-03-01 19:50:11 +00:00
|
|
|
});
|