Skip to content

Commit

Permalink
test(e2e): add for onboarding and lldap authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
Meierschlumpf committed Jan 2, 2025
1 parent cc7d53e commit f1f1bf2
Show file tree
Hide file tree
Showing 8 changed files with 477 additions and 33 deletions.
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ README.md
.next
.git
dev
.build
.build
e2e
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ apps/websocket/wssServer.cjs
apps/nextjs/.million/
packages/cli/cli.cjs

# e2e mounts
e2e/shared/tmp

#personal backgrounds
apps/nextjs/public/images/background.png
101 changes: 101 additions & 0 deletions e2e/lldap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { chromium } from "playwright";
import { GenericContainer } from "testcontainers";
import { describe, expect, test } from "vitest";

import * as sqliteSchema from "../packages/db/schema/sqlite";
import { createHomarrContainer, withLogs } from "./shared/create-homarr-container";
import { createSqliteDbFileAsync } from "./shared/e2e-db";

const defaultCredentials = {
username: "admin",
password: "password",
email: "[email protected]",
group: "lldap_admin",
};

const ldapBase = "dc=example,dc=com";

describe("LLDAP authorization", () => {
test("Authorize with LLDAP successfully", async () => {
// Arrange
const lldapContainer = await createLldapContainer().start();
const { db, localMountPath } = await createSqliteDbFileAsync();
const homarrContainer = await createHomarrContainer({
environment: {
AUTH_PROVIDERS: "ldap",
AUTH_LDAP_URI: `ldap://host.docker.internal:${lldapContainer.getMappedPort(3890)}`,
AUTH_LDAP_BASE: ldapBase,
AUTH_LDAP_BIND_DN: `uid=${defaultCredentials.username},ou=People,${ldapBase}`,
AUTH_LDAP_BIND_PASSWORD: defaultCredentials.password,
},
mounts: {
"/appdata": localMountPath,
},
}).start();

// Skip onboarding
await db.update(sqliteSchema.onboarding).set({
step: "finish",
});
await db.insert(sqliteSchema.groups).values({
id: "1",
name: defaultCredentials.group,
});

// Act
const browser = await chromium.launch();

Check failure on line 46 in e2e/lldap.spec.ts

View workflow job for this annotation

GitHub Actions / e2e

e2e/lldap.spec.ts > LLDAP authorization > Authorize with LLDAP successfully

Error: browserType.launch: Executable doesn't exist at /home/runner/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell ╔═════════════════════════════════════════════════════════════════════════╗ ║ Looks like Playwright Test or Playwright was just installed or updated. ║ ║ Please run the following command to download new browsers: ║ ║ ║ ║ pnpm exec playwright install ║ ║ ║ ║ <3 Playwright Team ║ ╚═════════════════════════════════════════════════════════════════════════╝ ❯ e2e/lldap.spec.ts:46:36
const context = await browser.newContext();
const page = await context.newPage();

// Login
page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}/auth/login`);
await page.getByLabel("Username").fill(defaultCredentials.username);
await page.getByLabel("Password").fill(defaultCredentials.password);
await page.locator("css=button[type='submit']").click();

// Wait for redirect
await page.waitForURL(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`, {
timeout: 10000,
});

// Assert
const users = await db.query.users.findMany({
with: {
groups: {
with: {
group: true,
},
},
},
});
expect(users).toHaveLength(1);
const user = users[0]!;
expect(user).toEqual(
expect.objectContaining({
name: defaultCredentials.username,
email: defaultCredentials.email,
provider: "ldap",
}),
);

const groups = user.groups.map((g) => g.group.name);
expect(groups).toContain(defaultCredentials.group);

// Cleanup
await browser.close();
await homarrContainer.stop();
await lldapContainer.stop();
}, 120_000);
});

const createLldapContainer = () => {
return withLogs(
new GenericContainer("lldap/lldap:stable").withExposedPorts(3890).withEnvironment({
LLDAP_JWT_SECRET: "REPLACE_WITH_RANDOM",
LLDAP_KEY_SEED: "REPLACE_WITH_RANDOM",
LLDAP_LDAP_BASE_DN: ldapBase,
LLDAP_LDAP_USER_PASS: defaultCredentials.password,
LLDAP_LDAP_USER_EMAIL: defaultCredentials.email,
}),
);
};
167 changes: 167 additions & 0 deletions e2e/onboarding/onboarding-steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { eq } from "drizzle-orm";
import { Page } from "playwright";
import { expect } from "vitest";

import * as sqliteSchema from "../../packages/db/schema/sqlite";
import { credentialsAdminGroup, OnboardingStep } from "../../packages/definitions/src";
import { SqliteDatabase } from "../shared/e2e-db";

const buttonTexts = {
fromScratch: "scratch",
oldmarrImport: "before 1.0",
};

class OnboardingStartStep {
private readonly page: Page;

constructor(page: Page) {
this.page = page;
}

public async pressButtonAsync(button: keyof typeof buttonTexts) {
await this.page
.locator("button", {
hasText: buttonTexts[button],
})
.click();
}
}

class OnboardingUserStep {
private readonly page: Page;

constructor(page: Page) {
this.page = page;
}

public async waitUntilReadyAsync() {
await this.page.waitForSelector("text=administrator user");
}

public async fillFormAsync(input: { username: string; password: string; confirmPassword: string }) {
await this.page.getByLabel("Username").fill(input.username);
await this.page.getByLabel("Password", { exact: true }).fill(input.password);
await this.page.getByLabel("Confirm password").fill(input.confirmPassword);
}

public async submitAsync() {
await this.page.locator("css=button[type='submit']").click();
}

public async assertUserAndAdminGroupInsertedAsync(db: SqliteDatabase, expectedUsername: string) {
const users = await db.query.users.findMany({
with: {
groups: {
with: {
group: {
with: {
permissions: true,
},
},
},
},
},
});
expect(users).toHaveLength(1);
const user = users[0]!;
expect(user).toEqual(
expect.objectContaining({
name: expectedUsername,
provider: "credentials",
}),
);
expect(user.password).not.toBeNull();
expect(user.salt).not.toBeNull();

const groups = user.groups.map((g) => g.group);
expect(groups).toHaveLength(1);
expect(groups[0].name).toEqual(credentialsAdminGroup);
expect(groups[0].permissions).toEqual([expect.objectContaining({ permission: "admin" })]);
}
}

class OnboardingGroupStep {
private readonly page: Page;

constructor(page: Page) {
this.page = page;
}

public async waitUntilReadyAsync() {
await this.page.waitForSelector("text=external provider");
}

public async fillGroupAsync(groupName: string) {
await this.page.locator("input").fill(groupName);
}

public async submitAsync() {
await this.page.locator("css=button[type='submit']").click();
}

public async assertGroupInsertedAsync(db: SqliteDatabase, expectedGroupName: string) {
const group = await db.query.groups.findFirst({
where: eq(sqliteSchema.groups.name, expectedGroupName),
with: {
permissions: true,
},
});
expect(group).not.toBeUndefined();
expect(group?.permissions).toEqual([expect.objectContaining({ permission: "admin" })]);
}
}

class OnboardingSettingsStep {
private readonly page: Page;

constructor(page: Page) {
this.page = page;
}

public async waitUntilReadyAsync() {
await this.page.waitForSelector("text=Analytics");
}

public async submitAsync() {
await this.page.locator("css=button[type='submit']").click();
}
}

class OnboardingFinishStep {
private readonly page: Page;

constructor(page: Page) {
this.page = page;
}

public async waitUntilReadyAsync() {
await this.page.waitForSelector("text=completed the setup");
}
}

export class Onboarding {
private readonly page: Page;
public readonly steps: {
start: OnboardingStartStep;
user: OnboardingUserStep;
group: OnboardingGroupStep;
settings: OnboardingSettingsStep;
finish: OnboardingFinishStep;
};

constructor(page: Page) {
this.page = page;
this.steps = {
start: new OnboardingStartStep(this.page),
user: new OnboardingUserStep(this.page),
group: new OnboardingGroupStep(this.page),
settings: new OnboardingSettingsStep(this.page),
finish: new OnboardingFinishStep(this.page),
};
}

public async assertOnboardingStepAsync(db: SqliteDatabase, expectedStep: OnboardingStep) {
const onboarding = await db.query.onboarding.findFirst();
expect(onboarding?.step).toEqual(expectedStep);
}
}
92 changes: 92 additions & 0 deletions e2e/onboarding/onboarding.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { chromium } from "playwright";
import { describe, test } from "vitest";

import { createHomarrContainer } from "../shared/create-homarr-container";
import { createSqliteDbFileAsync } from "../shared/e2e-db";
import { Onboarding } from "./onboarding-steps";

describe("Onboarding", () => {
test("Credentials onboarding should be successful", async () => {
// Arrange
const { db, localMountPath } = await createSqliteDbFileAsync();
const homarrContainer = await createHomarrContainer({
mounts: {
"/appdata": localMountPath,
},
}).start();

// Act
const browser = await chromium.launch();

Check failure on line 19 in e2e/onboarding/onboarding.spec.ts

View workflow job for this annotation

GitHub Actions / e2e

e2e/onboarding/onboarding.spec.ts > Onboarding > Credentials onboarding should be successful

Error: browserType.launch: Executable doesn't exist at /home/runner/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell ╔═════════════════════════════════════════════════════════════════════════╗ ║ Looks like Playwright Test or Playwright was just installed or updated. ║ ║ Please run the following command to download new browsers: ║ ║ ║ ║ pnpm exec playwright install ║ ║ ║ ║ <3 Playwright Team ║ ╚═════════════════════════════════════════════════════════════════════════╝ ❯ e2e/onboarding/onboarding.spec.ts:19:36
const context = await browser.newContext();
const page = await context.newPage();
const onboarding = new Onboarding(page);

await page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`);
await onboarding.steps.start.pressButtonAsync("fromScratch");

await onboarding.steps.user.waitUntilReadyAsync();
await onboarding.steps.user.fillFormAsync({
username: "admin",
password: "Comp(exP4sswOrd",
confirmPassword: "Comp(exP4sswOrd",
});
await onboarding.steps.user.submitAsync();

await onboarding.steps.settings.waitUntilReadyAsync();
await onboarding.steps.settings.submitAsync();

await onboarding.steps.finish.waitUntilReadyAsync();

// Assert
await onboarding.steps.user.assertUserAndAdminGroupInsertedAsync(db, "admin");
await onboarding.assertOnboardingStepAsync(db, "finish");

// Cleanup
await browser.close();
await homarrContainer.stop();
}, 120_000);

test("External provider onboarding setup should be successful", async () => {
// Arrange
const { db, localMountPath } = await createSqliteDbFileAsync();
const homarrContainer = await createHomarrContainer({
environment: {
AUTH_PROVIDERS: "ldap",
AUTH_LDAP_URI: "ldap://host.docker.internal:3890",
AUTH_LDAP_BASE: "",
AUTH_LDAP_BIND_DN: "",
AUTH_LDAP_BIND_PASSWORD: "",
},
mounts: {
"/appdata": localMountPath,
},
}).start();
const externalGroupName = "oidc-admins";

// Act
const browser = await chromium.launch();

Check failure on line 67 in e2e/onboarding/onboarding.spec.ts

View workflow job for this annotation

GitHub Actions / e2e

e2e/onboarding/onboarding.spec.ts > Onboarding > External provider onboarding setup should be successful

Error: browserType.launch: Executable doesn't exist at /home/runner/.cache/ms-playwright/chromium_headless_shell-1148/chrome-linux/headless_shell ╔═════════════════════════════════════════════════════════════════════════╗ ║ Looks like Playwright Test or Playwright was just installed or updated. ║ ║ Please run the following command to download new browsers: ║ ║ ║ ║ pnpm exec playwright install ║ ║ ║ ║ <3 Playwright Team ║ ╚═════════════════════════════════════════════════════════════════════════╝ ❯ e2e/onboarding/onboarding.spec.ts:67:36
const context = await browser.newContext();
const page = await context.newPage();
const onboarding = new Onboarding(page);

await page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`);
await onboarding.steps.start.pressButtonAsync("fromScratch");

await onboarding.steps.group.waitUntilReadyAsync();
await onboarding.steps.group.fillGroupAsync(externalGroupName);
await onboarding.steps.group.submitAsync();

await onboarding.steps.settings.waitUntilReadyAsync();
await onboarding.steps.settings.submitAsync();

await onboarding.steps.finish.waitUntilReadyAsync();

// Assert
await onboarding.steps.group.assertGroupInsertedAsync(db, externalGroupName);
await onboarding.assertOnboardingStepAsync(db, "finish");

// Cleanup
await browser.close();
await homarrContainer.stop();
}, 120_000);
});
Loading

0 comments on commit f1f1bf2

Please sign in to comment.