-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(e2e): add for onboarding and lldap authorization
- Loading branch information
1 parent
cc7d53e
commit f1f1bf2
Showing
8 changed files
with
477 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,5 @@ README.md | |
.next | ||
.git | ||
dev | ||
.build | ||
.build | ||
e2e |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 GitHub Actions / e2ee2e/lldap.spec.ts > LLDAP authorization > Authorize with LLDAP successfully
|
||
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, | ||
}), | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 GitHub Actions / e2ee2e/onboarding/onboarding.spec.ts > Onboarding > Credentials onboarding should be successful
|
||
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 GitHub Actions / e2ee2e/onboarding/onboarding.spec.ts > Onboarding > External provider onboarding setup should be successful
|
||
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); | ||
}); |
Oops, something went wrong.