mirror of
https://github.com/ajnart/homarr.git
synced 2026-01-30 19:29:17 +01:00
235 lines
7.6 KiB
TypeScript
235 lines
7.6 KiB
TypeScript
import type { StartedTestContainer } from "testcontainers";
|
|
import { GenericContainer, Wait } from "testcontainers";
|
|
import { describe, expect, test, vi } from "vitest";
|
|
|
|
import { createDb } from "@homarr/db/test";
|
|
|
|
import { PiHoleIntegrationV5, PiHoleIntegrationV6 } from "../src";
|
|
import type { SessionStore } from "../src/base/session-store";
|
|
import { TestConnectionError } from "../src/base/test-connection/test-connection-error";
|
|
|
|
vi.mock("@homarr/db", async (importActual) => {
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
const actual = await importActual<typeof import("@homarr/db")>();
|
|
return {
|
|
...actual,
|
|
db: createDb(),
|
|
};
|
|
});
|
|
|
|
const DEFAULT_PASSWORD = "12341234";
|
|
const DEFAULT_API_KEY = "3b1434980677dcf53fa8c4a611db3b1f0f88478790097515c0abb539102778b9"; // Some hash generated from password
|
|
|
|
describe("Pi-hole v5 integration", () => {
|
|
test("getSummaryAsync should return summary from pi-hole", async () => {
|
|
// Arrange
|
|
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV5(piholeContainer, DEFAULT_API_KEY);
|
|
|
|
// Act
|
|
const result = await piHoleIntegration.getSummaryAsync();
|
|
|
|
// Assert
|
|
expect(result.adsBlockedToday).toBe(0);
|
|
expect(result.adsBlockedTodayPercentage).toBe(0);
|
|
expect(result.dnsQueriesToday).toBe(0);
|
|
expect(result.domainsBeingBlocked).toBeGreaterThan(1);
|
|
|
|
// Cleanup
|
|
await piholeContainer.stop();
|
|
}, 20_000); // Timeout of 20 seconds
|
|
|
|
test("testConnectionAsync should be successful", async () => {
|
|
// Arrange
|
|
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV5(piholeContainer, DEFAULT_API_KEY);
|
|
|
|
// Act
|
|
const result = await piHoleIntegration.testConnectionAsync();
|
|
|
|
// Assert
|
|
expect(result.success).toBe(true);
|
|
|
|
// Cleanup
|
|
await piholeContainer.stop();
|
|
}, 20_000); // Timeout of 20 seconds
|
|
|
|
test("testConnectionAsync should fail with unauthorized for wrong credentials", async () => {
|
|
// Arrange
|
|
const piholeContainer = await createPiHoleV5Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV5(piholeContainer, "wrong-api-key");
|
|
|
|
// Act
|
|
const result = await piHoleIntegration.testConnectionAsync();
|
|
|
|
// Assert
|
|
expect(result.success).toBe(false);
|
|
if (result.success) return;
|
|
expect(result.error).toBeInstanceOf(TestConnectionError);
|
|
expect(result.error.type).toBe("authorization");
|
|
|
|
// Cleanup
|
|
await piholeContainer.stop();
|
|
}, 20_000); // Timeout of 20 seconds
|
|
});
|
|
|
|
vi.mock("../src/base/session-store", () => ({
|
|
createSessionStore: () =>
|
|
({
|
|
async getAsync() {
|
|
return await Promise.resolve(null);
|
|
},
|
|
async setAsync() {
|
|
return await Promise.resolve();
|
|
},
|
|
async clearAsync() {
|
|
return await Promise.resolve();
|
|
},
|
|
}) satisfies SessionStore<string>,
|
|
}));
|
|
|
|
describe("Pi-hole v6 integration", () => {
|
|
test("getSummaryAsync should return summary from pi-hole", async () => {
|
|
// Arrange
|
|
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
|
|
|
// Act
|
|
const result = await piHoleIntegration.getSummaryAsync();
|
|
|
|
// Assert
|
|
expect(result.status).toBe("enabled");
|
|
expect(result.adsBlockedToday).toBe(0);
|
|
expect(result.adsBlockedTodayPercentage).toBe(0);
|
|
expect(result.dnsQueriesToday).toBe(0);
|
|
expect(result.domainsBeingBlocked).toBeGreaterThanOrEqual(0);
|
|
|
|
// Cleanup
|
|
await piholeContainer.stop();
|
|
}, 20_000); // Timeout of 20 seconds
|
|
|
|
test("enableAsync should enable pi-hole", async () => {
|
|
// Arrange
|
|
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
|
|
|
// Disable pi-hole
|
|
await piholeContainer.exec(["pihole", "disable"]);
|
|
|
|
// Act
|
|
await piHoleIntegration.enableAsync();
|
|
|
|
// Assert
|
|
const status = await piHoleIntegration.getDnsBlockingStatusAsync();
|
|
expect(status.blocking).toContain("enabled");
|
|
}, 20_000); // Timeout of 20 seconds
|
|
|
|
test("disableAsync should disable pi-hole", async () => {
|
|
// Arrange
|
|
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
|
|
|
// Act
|
|
await piHoleIntegration.disableAsync();
|
|
|
|
// Assert
|
|
const status = await piHoleIntegration.getDnsBlockingStatusAsync();
|
|
expect(status.blocking).toBe("disabled");
|
|
expect(status.timer).toBe(null);
|
|
}, 20_000); // Timeout of 20 seconds
|
|
|
|
test("disableAsync should disable pi-hole with timer", async () => {
|
|
// Arrange
|
|
const timer = 10 * 60; // 10 minutes
|
|
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
|
|
|
// Act
|
|
await piHoleIntegration.disableAsync(timer);
|
|
|
|
// Assert
|
|
const status = await piHoleIntegration.getDnsBlockingStatusAsync();
|
|
expect(status.blocking).toBe("disabled");
|
|
expect(status.timer).toBeGreaterThan(timer - 10);
|
|
}, 20_000); // Timeout of 20 seconds
|
|
|
|
test("testConnectionAsync should be successful", async () => {
|
|
// Arrange
|
|
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, DEFAULT_PASSWORD);
|
|
|
|
// Act
|
|
const result = await piHoleIntegration.testConnectionAsync();
|
|
|
|
// Assert
|
|
expect(result.success).toBe(true);
|
|
|
|
// Cleanup
|
|
await piholeContainer.stop();
|
|
}, 20_000); // Timeout of 20 seconds
|
|
|
|
test("testConnectionAsync should fail with unauthorized for wrong credentials", async () => {
|
|
// Arrange
|
|
const piholeContainer = await createPiHoleV6Container(DEFAULT_PASSWORD).start();
|
|
const piHoleIntegration = createPiHoleIntegrationV6(piholeContainer, "wrong-api-key");
|
|
|
|
// Act
|
|
const result = await piHoleIntegration.testConnectionAsync();
|
|
|
|
// Assert
|
|
expect(result.success).toBe(false);
|
|
if (result.success) return;
|
|
expect(result.error).toBeInstanceOf(TestConnectionError);
|
|
expect(result.error.type).toBe("authorization");
|
|
|
|
// Cleanup
|
|
await piholeContainer.stop();
|
|
}, 20_000); // Timeout of 20 seconds
|
|
});
|
|
|
|
const createPiHoleV5Container = (password: string) => {
|
|
return new GenericContainer("pihole/pihole:2024.07.0") // v5
|
|
.withEnvironment({
|
|
WEBPASSWORD: password,
|
|
})
|
|
.withExposedPorts(80)
|
|
.withWaitStrategy(Wait.forLogMessage("Pi-hole Enabled"));
|
|
};
|
|
|
|
const createPiHoleIntegrationV5 = (container: StartedTestContainer, apiKey: string) => {
|
|
return new PiHoleIntegrationV5({
|
|
id: "1",
|
|
decryptedSecrets: [
|
|
{
|
|
kind: "apiKey",
|
|
value: apiKey,
|
|
},
|
|
],
|
|
name: "Pi hole",
|
|
url: `http://${container.getHost()}:${container.getMappedPort(80)}`,
|
|
});
|
|
};
|
|
|
|
const createPiHoleV6Container = (password: string) => {
|
|
return new GenericContainer("pihole/pihole:latest")
|
|
.withEnvironment({
|
|
FTLCONF_webserver_api_password: password,
|
|
})
|
|
.withExposedPorts(80)
|
|
.withWaitStrategy(Wait.forHttp("/admin", 80));
|
|
};
|
|
|
|
const createPiHoleIntegrationV6 = (container: StartedTestContainer, apiKey: string) => {
|
|
return new PiHoleIntegrationV6({
|
|
id: "1",
|
|
decryptedSecrets: [
|
|
{
|
|
kind: "apiKey",
|
|
value: apiKey,
|
|
},
|
|
],
|
|
name: "Pi hole",
|
|
url: `http://${container.getHost()}:${container.getMappedPort(80)}`,
|
|
});
|
|
};
|