mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-10-22 03:14:06 -05:00
chore(playwright): remove unnecessary global auth checks (#8341)
This commit is contained in:
1 parent
faa97e92e8
commit
094d7a2d02
17 files changed
+379
-243
No files matched your search
@@ -17,7 +17,7 @@ test.describe("Default Assistant Admin Page", () => {
|
||||
await page.context().clearCookies();
|
||||
await loginAs(page, "admin");
|
||||
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
|
||||
// Create a connector so Internal Search tool becomes available
|
||||
testCcPairId = await apiClient.createFileConnector(
|
||||
@@ -83,7 +83,7 @@ test.describe("Default Assistant Admin Page", () => {
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
|
||||
// Clean up the test connector
|
||||
if (testCcPairId !== null) {
|
||||
|
||||
@@ -153,7 +153,7 @@ export const test = base.extend<{
|
||||
// API client fixture - provides access to OnyxApiClient for backend operations
|
||||
apiClient: async ({ page }, use) => {
|
||||
await authenticateAdmin(page);
|
||||
const client = new OnyxApiClient(page);
|
||||
const client = new OnyxApiClient(page.request);
|
||||
await use(client);
|
||||
},
|
||||
|
||||
@@ -161,7 +161,7 @@ export const test = base.extend<{
|
||||
seededGuild: async ({ page }, use) => {
|
||||
await authenticateAdmin(page);
|
||||
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
const guild = await apiClient.createDiscordGuild();
|
||||
|
||||
await use({
|
||||
|
||||
@@ -89,7 +89,7 @@ test.describe("Image Generation Provider Configuration", () => {
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// Clean up the image generation config created during the test
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
try {
|
||||
await apiClient.deleteImageGenerationConfig("openai_dalle_3");
|
||||
console.log("[image-gen-test] Cleaned up DALL-E 3 config");
|
||||
|
||||
@@ -16,6 +16,21 @@ test.describe("Appearance Theme Settings", () => {
|
||||
await page.context().clearCookies();
|
||||
await loginAs(page, "admin");
|
||||
|
||||
// Navigate first so localStorage is accessible (API-based login
|
||||
// doesn't navigate, leaving the page on about:blank).
|
||||
await page.goto("/admin/theme");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Skip the entire test when Enterprise features are not licensed.
|
||||
// The /admin/theme page is gated behind ee_features_enabled and
|
||||
// renders a license-required message instead of the settings form.
|
||||
const eeLocked = page.getByText(
|
||||
"This functionality requires an active Enterprise license."
|
||||
);
|
||||
if (await eeLocked.isVisible({ timeout: 1000 }).catch(() => false)) {
|
||||
test.skip(true, "Enterprise license not active — skipping theme tests");
|
||||
}
|
||||
|
||||
// Clear localStorage to ensure consent modal shows
|
||||
await page.evaluate(() => {
|
||||
localStorage.removeItem("allUsersInitialPopupFlowCompleted");
|
||||
@@ -24,11 +39,17 @@ test.describe("Appearance Theme Settings", () => {
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// Reset settings to defaults
|
||||
await page.goto("http://localhost:3000/admin/theme");
|
||||
await page.goto("/admin/theme");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Clear form fields
|
||||
// If the form isn't visible (e.g. EE license not active, or test failed
|
||||
// before navigating here), skip cleanup — there's nothing to reset.
|
||||
const appNameInput = page.locator('[data-label="application-name-input"]');
|
||||
if (!(await appNameInput.isVisible({ timeout: 3000 }).catch(() => false))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear form fields
|
||||
await appNameInput.clear();
|
||||
|
||||
const greetingInput = page.locator('[data-label="greeting-message-input"]');
|
||||
@@ -70,11 +91,7 @@ test.describe("Appearance Theme Settings", () => {
|
||||
test("admin configures branding and verifies across pages", async ({
|
||||
page,
|
||||
}) => {
|
||||
// 1. Navigate to theme page
|
||||
await page.goto("http://localhost:3000/admin/theme");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// 2. Fill in Application Name
|
||||
// 1. Fill in Application Name (page already navigated in beforeEach)
|
||||
const appNameInput = page.locator('[data-label="application-name-input"]');
|
||||
await appNameInput.fill(TEST_VALUES.applicationName);
|
||||
|
||||
@@ -154,7 +171,7 @@ test.describe("Appearance Theme Settings", () => {
|
||||
await page.evaluate(() => {
|
||||
localStorage.removeItem("allUsersInitialPopupFlowCompleted");
|
||||
});
|
||||
await page.goto("http://localhost:3000/app");
|
||||
await page.goto("/app");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// 16. Handle consent modal
|
||||
|
||||
@@ -180,7 +180,7 @@ test.describe("Assistant Creation and Edit Verification", () => {
|
||||
storageState: "admin_auth.json",
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const cleanupClient = new OnyxApiClient(page);
|
||||
const cleanupClient = new OnyxApiClient(page.request);
|
||||
await cleanupClient.deleteDocumentSet(documentSetId);
|
||||
await cleanupClient.deleteCCPair(ccPairId);
|
||||
await context.close();
|
||||
@@ -200,7 +200,7 @@ test.describe("Assistant Creation and Edit Verification", () => {
|
||||
await loginAs(page, "admin");
|
||||
|
||||
// Create a connector and document set to enable the Knowledge toggle
|
||||
const onyxApiClient = new OnyxApiClient(page);
|
||||
const onyxApiClient = new OnyxApiClient(page.request);
|
||||
ccPairId = await onyxApiClient.createFileConnector("Test Connector");
|
||||
documentSetId = await onyxApiClient.createDocumentSet(
|
||||
"Test Document Set",
|
||||
|
||||
@@ -50,7 +50,7 @@ test("Restricted LLM Provider should not appear for unauthorized users", async (
|
||||
let groupId: number | null = null;
|
||||
let providerId: number | null = null;
|
||||
|
||||
const client = new OnyxApiClient(page);
|
||||
const client = new OnyxApiClient(page.request);
|
||||
|
||||
try {
|
||||
groupId = await client.createUserGroup(restrictedGroupName);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { TEST_ADMIN_CREDENTIALS, TEST_USER_CREDENTIALS } from "../constants";
|
||||
|
||||
// These tests exercise the browser login UI.
|
||||
// They clear cookies to start unauthenticated, then drive the login form.
|
||||
|
||||
test.describe("Login flow", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.context().clearCookies();
|
||||
});
|
||||
|
||||
test("Login page renders email and password fields", async ({ page }) => {
|
||||
await page.goto("/auth/login");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await expect(page.getByTestId("email")).toBeVisible();
|
||||
await expect(page.getByTestId("password")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Sign In" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("User can log in with valid credentials", async ({ page }) => {
|
||||
const { email, password } = TEST_ADMIN_CREDENTIALS;
|
||||
|
||||
await page.goto("/auth/login");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.getByTestId("email").fill(email);
|
||||
await page.getByTestId("password").fill(password);
|
||||
await page.getByRole("button", { name: "Sign In" }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/app/);
|
||||
|
||||
// Verify the session is valid
|
||||
const me = await page.request.get("/api/me");
|
||||
expect(me.ok()).toBe(true);
|
||||
const body = await me.json();
|
||||
expect(body.email).toBe(email);
|
||||
});
|
||||
|
||||
test("Login fails with invalid password", async ({ page }) => {
|
||||
await page.goto("/auth/login");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.getByTestId("email").fill(TEST_USER_CREDENTIALS.email);
|
||||
await page.getByTestId("password").fill("WrongPassword123!");
|
||||
await page.getByRole("button", { name: "Sign In" }).click();
|
||||
|
||||
// Should stay on the login page
|
||||
await expect(page).toHaveURL(/\/auth\/login/);
|
||||
|
||||
// Should not be authenticated
|
||||
const me = await page.request.get("/api/me");
|
||||
expect(me.ok()).toBe(false);
|
||||
});
|
||||
|
||||
test("Login fails with non-existent user", async ({ page }) => {
|
||||
await page.goto("/auth/login");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.getByTestId("email").fill("nonexistent@example.com");
|
||||
await page.getByTestId("password").fill("SomePassword123!");
|
||||
await page.getByRole("button", { name: "Sign In" }).click();
|
||||
|
||||
// Should stay on the login page
|
||||
await expect(page).toHaveURL(/\/auth\/login/);
|
||||
});
|
||||
});
|
||||
@@ -42,7 +42,7 @@ test.describe("Chat Search Command Menu", () => {
|
||||
storageState: "admin_auth.json",
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const client = new OnyxApiClient(page);
|
||||
const client = new OnyxApiClient(page.request);
|
||||
|
||||
// Navigate to app to establish session
|
||||
await page.goto("/app");
|
||||
@@ -69,7 +69,7 @@ test.describe("Chat Search Command Menu", () => {
|
||||
storageState: "admin_auth.json",
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const client = new OnyxApiClient(page);
|
||||
const client = new OnyxApiClient(page.request);
|
||||
|
||||
// Navigate to app to establish session
|
||||
await page.goto("/app");
|
||||
|
||||
@@ -29,7 +29,7 @@ test.describe("Default Assistant Tests", () => {
|
||||
await adminPage.goto("http://localhost:3000/app");
|
||||
await adminPage.waitForLoadState("networkidle");
|
||||
|
||||
const apiClient = new OnyxApiClient(adminPage);
|
||||
const apiClient = new OnyxApiClient(adminPage.request);
|
||||
try {
|
||||
imageGenConfigId = await apiClient.createImageGenerationConfig(
|
||||
`test-default-assistant-${Date.now()}`
|
||||
@@ -51,7 +51,7 @@ test.describe("Default Assistant Tests", () => {
|
||||
await adminPage.goto("http://localhost:3000/app");
|
||||
await adminPage.waitForLoadState("networkidle");
|
||||
|
||||
const apiClient = new OnyxApiClient(adminPage);
|
||||
const apiClient = new OnyxApiClient(adminPage.request);
|
||||
await apiClient.deleteImageGenerationConfig(imageGenConfigId);
|
||||
|
||||
await adminContext.close();
|
||||
@@ -317,7 +317,7 @@ test.describe("Default Assistant Tests", () => {
|
||||
await adminPage.goto("http://localhost:3000/app");
|
||||
await adminPage.waitForLoadState("networkidle");
|
||||
|
||||
const apiClient = new OnyxApiClient(adminPage);
|
||||
const apiClient = new OnyxApiClient(adminPage.request);
|
||||
try {
|
||||
imageGenConfigId = await apiClient.createImageGenerationConfig(
|
||||
`test-action-toggle-${Date.now()}`
|
||||
@@ -339,7 +339,7 @@ test.describe("Default Assistant Tests", () => {
|
||||
await adminPage.goto("http://localhost:3000/app");
|
||||
await adminPage.waitForLoadState("networkidle");
|
||||
|
||||
const apiClient = new OnyxApiClient(adminPage);
|
||||
const apiClient = new OnyxApiClient(adminPage.request);
|
||||
await apiClient.deleteImageGenerationConfig(imageGenConfigId);
|
||||
|
||||
await adminContext.close();
|
||||
@@ -364,7 +364,7 @@ test.describe("Default Assistant Tests", () => {
|
||||
await page.goto("/app");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
let webSearchProviderId: number | null = null;
|
||||
|
||||
try {
|
||||
@@ -555,7 +555,7 @@ test.describe("End-to-End Default Assistant Flow", () => {
|
||||
await adminPage.goto("http://localhost:3000/app");
|
||||
await adminPage.waitForLoadState("networkidle");
|
||||
|
||||
const apiClient = new OnyxApiClient(adminPage);
|
||||
const apiClient = new OnyxApiClient(adminPage.request);
|
||||
try {
|
||||
imageGenConfigId = await apiClient.createImageGenerationConfig(
|
||||
`test-e2e-journey-${Date.now()}`
|
||||
@@ -577,7 +577,7 @@ test.describe("End-to-End Default Assistant Flow", () => {
|
||||
await adminPage.goto("http://localhost:3000/app");
|
||||
await adminPage.waitForLoadState("networkidle");
|
||||
|
||||
const apiClient = new OnyxApiClient(adminPage);
|
||||
const apiClient = new OnyxApiClient(adminPage.request);
|
||||
await apiClient.deleteImageGenerationConfig(imageGenConfigId);
|
||||
|
||||
await adminContext.close();
|
||||
|
||||
@@ -11,7 +11,7 @@ test.describe("LLM Ordering", () => {
|
||||
await page.context().clearCookies();
|
||||
await loginAs(page, "admin");
|
||||
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
|
||||
// Create image generation config so the checkbox appears
|
||||
try {
|
||||
@@ -24,7 +24,7 @@ test.describe("LLM Ordering", () => {
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
|
||||
if (imageGenConfigId !== null) {
|
||||
try {
|
||||
|
||||
@@ -47,14 +47,14 @@ test.describe("InlineFileManagement", () => {
|
||||
await page.context().clearCookies();
|
||||
await loginAs(page, "admin");
|
||||
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
testCcPairId = await apiClient.createFileConnector(
|
||||
`Test File Connector ${Date.now()}`
|
||||
);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
const apiClient = new OnyxApiClient(page);
|
||||
const apiClient = new OnyxApiClient(page.request);
|
||||
|
||||
if (testCcPairId !== null) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Playwright fixture that ensures a public LLM provider is available.
|
||||
*
|
||||
* Usage:
|
||||
* ```ts
|
||||
* // Import from this file instead of @playwright/test
|
||||
* import { test, expect } from "@tests/e2e/fixtures/llmProvider";
|
||||
*
|
||||
* test("my test that needs an LLM provider", async ({ page, llmProviderId }) => {
|
||||
* // llmProviderId is the ID of the provider that was created (or null if
|
||||
* // one already existed). The fixture handles cleanup automatically.
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* The fixture:
|
||||
* - Authenticates as admin
|
||||
* - Creates a public LLM provider if none exists
|
||||
* - Provides the created provider ID to the test
|
||||
* - Cleans up the provider after all tests in the file complete
|
||||
*/
|
||||
|
||||
import { test as base, expect } from "@playwright/test";
|
||||
import { loginAs } from "@tests/e2e/utils/auth";
|
||||
import { OnyxApiClient } from "@tests/e2e/utils/onyxApiClient";
|
||||
|
||||
export const test = base.extend<{
|
||||
/**
|
||||
* The ID of the public LLM provider created by this fixture, or `null`
|
||||
* if a public provider already existed.
|
||||
*/
|
||||
llmProviderId: number | null;
|
||||
}>({
|
||||
llmProviderId: async ({ page }, use) => {
|
||||
// Authenticate as admin to be able to create/list providers
|
||||
await page.context().clearCookies();
|
||||
await loginAs(page, "admin");
|
||||
|
||||
const client = new OnyxApiClient(page.request);
|
||||
const createdId = await client.ensurePublicProvider();
|
||||
await use(createdId);
|
||||
|
||||
// Cleanup: only delete if we created one
|
||||
if (createdId !== null) {
|
||||
// Re-authenticate in case the test changed the session
|
||||
await page.context().clearCookies();
|
||||
await loginAs(page, "admin");
|
||||
await client.deleteProvider(createdId);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export { expect };
|
||||
@@ -1,10 +1,10 @@
|
||||
import { chromium, FullConfig, request } from "@playwright/test";
|
||||
import { loginAs } from "./utils/auth";
|
||||
import { FullConfig, request } from "@playwright/test";
|
||||
import {
|
||||
TEST_ADMIN_CREDENTIALS,
|
||||
TEST_ADMIN2_CREDENTIALS,
|
||||
TEST_USER_CREDENTIALS,
|
||||
} from "./constants";
|
||||
import { OnyxApiClient } from "./utils/onyxApiClient";
|
||||
|
||||
const PREFLIGHT_TIMEOUT_MS = 60_000;
|
||||
const PREFLIGHT_POLL_INTERVAL_MS = 2_000;
|
||||
@@ -78,6 +78,34 @@ async function ensureUserExists(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log in via the API and save the resulting cookies as a Playwright storage
|
||||
* state file. No browser is needed — this uses Playwright's lightweight
|
||||
* request context, which is much faster and produces no console noise.
|
||||
*/
|
||||
async function apiLoginAndSaveState(
|
||||
baseURL: string,
|
||||
email: string,
|
||||
password: string,
|
||||
storageStatePath: string
|
||||
): Promise<void> {
|
||||
const ctx = await request.newContext({ baseURL });
|
||||
try {
|
||||
const res = await ctx.post("/api/auth/login", {
|
||||
form: { username: email, password },
|
||||
});
|
||||
if (!res.ok()) {
|
||||
const body = await res.text();
|
||||
throw new Error(
|
||||
`[global-setup] Login failed for ${email}: ${res.status()} ${body}`
|
||||
);
|
||||
}
|
||||
await ctx.storageState({ path: storageStatePath });
|
||||
} finally {
|
||||
await ctx.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Promote a user to admin via the manage API.
|
||||
* Requires an authenticated context (admin storage state).
|
||||
@@ -147,14 +175,13 @@ async function globalSetup(config: FullConfig) {
|
||||
TEST_ADMIN2_CREDENTIALS.password
|
||||
);
|
||||
|
||||
// ── Login and save storage state ─────────────────────────────────────
|
||||
const browser = await chromium.launch();
|
||||
|
||||
const adminContext = await browser.newContext({ baseURL });
|
||||
const adminPage = await adminContext.newPage();
|
||||
await loginAs(adminPage, "admin");
|
||||
await adminContext.storageState({ path: "admin_auth.json" });
|
||||
await adminContext.close();
|
||||
// ── Login via API and save storage state ───────────────────────────
|
||||
await apiLoginAndSaveState(
|
||||
baseURL,
|
||||
TEST_ADMIN_CREDENTIALS.email,
|
||||
TEST_ADMIN_CREDENTIALS.password,
|
||||
"admin_auth.json"
|
||||
);
|
||||
|
||||
// Promote admin2 now that we have an admin session
|
||||
await promoteToAdmin(
|
||||
@@ -163,19 +190,33 @@ async function globalSetup(config: FullConfig) {
|
||||
TEST_ADMIN2_CREDENTIALS.email
|
||||
);
|
||||
|
||||
const userContext = await browser.newContext({ baseURL });
|
||||
const userPage = await userContext.newPage();
|
||||
await loginAs(userPage, "user");
|
||||
await userContext.storageState({ path: "user_auth.json" });
|
||||
await userContext.close();
|
||||
await apiLoginAndSaveState(
|
||||
baseURL,
|
||||
TEST_USER_CREDENTIALS.email,
|
||||
TEST_USER_CREDENTIALS.password,
|
||||
"user_auth.json"
|
||||
);
|
||||
|
||||
const admin2Context = await browser.newContext({ baseURL });
|
||||
const admin2Page = await admin2Context.newPage();
|
||||
await loginAs(admin2Page, "admin2");
|
||||
await admin2Context.storageState({ path: "admin2_auth.json" });
|
||||
await admin2Context.close();
|
||||
await apiLoginAndSaveState(
|
||||
baseURL,
|
||||
TEST_ADMIN2_CREDENTIALS.email,
|
||||
TEST_ADMIN2_CREDENTIALS.password,
|
||||
"admin2_auth.json"
|
||||
);
|
||||
|
||||
await browser.close();
|
||||
// ── Ensure a public LLM provider exists ───────────────────────────
|
||||
// Many tests depend on a default LLM being configured (file uploads,
|
||||
// assistant creation, etc.). Re-use the admin session we just saved.
|
||||
const adminCtx = await request.newContext({
|
||||
baseURL,
|
||||
storageState: "admin_auth.json",
|
||||
});
|
||||
try {
|
||||
const client = new OnyxApiClient(adminCtx, baseURL);
|
||||
await client.ensurePublicProvider();
|
||||
} finally {
|
||||
await adminCtx.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import type { Page } from "@playwright/test";
|
||||
import { loginAs, loginWithCredentials } from "../utils/auth";
|
||||
import { loginAs, apiLogin } from "../utils/auth";
|
||||
import { OnyxApiClient } from "../utils/onyxApiClient";
|
||||
import { startMcpApiKeyServer, McpServerProcess } from "../utils/mcpServer";
|
||||
|
||||
@@ -90,15 +90,10 @@ test.describe("Default Assistant MCP Integration", () => {
|
||||
storageState: "admin_auth.json",
|
||||
});
|
||||
const adminPage = await adminContext.newPage();
|
||||
const adminClient = new OnyxApiClient(adminPage);
|
||||
const adminClient = new OnyxApiClient(adminPage.request);
|
||||
|
||||
const providers = await adminClient.listLlmProviders();
|
||||
const hasPublicProvider = providers.some((provider) => provider.is_public);
|
||||
if (!hasPublicProvider) {
|
||||
createdProviderId = await adminClient.createPublicProvider(
|
||||
`PW Public Provider ${Date.now()}`
|
||||
);
|
||||
}
|
||||
// Ensure a public LLM provider exists
|
||||
createdProviderId = await adminClient.ensurePublicProvider();
|
||||
|
||||
// Clean up any existing servers with the same URL
|
||||
try {
|
||||
@@ -125,9 +120,9 @@ test.describe("Default Assistant MCP Integration", () => {
|
||||
storageState: "admin_auth.json",
|
||||
});
|
||||
const adminPage = await adminContext.newPage();
|
||||
const adminClient = new OnyxApiClient(adminPage);
|
||||
const adminClient = new OnyxApiClient(adminPage.request);
|
||||
|
||||
if (createdProviderId) {
|
||||
if (createdProviderId !== null) {
|
||||
await adminClient.deleteProvider(createdProviderId);
|
||||
}
|
||||
|
||||
@@ -361,7 +356,7 @@ test.describe("Default Assistant MCP Integration", () => {
|
||||
test.skip(!basicUserEmail, "Basic user must be created first");
|
||||
|
||||
await page.context().clearCookies();
|
||||
await loginWithCredentials(page, basicUserEmail, basicUserPassword);
|
||||
await apiLogin(page, basicUserEmail, basicUserPassword);
|
||||
console.log(`[test] Logged in as basic user: ${basicUserEmail}`);
|
||||
|
||||
// Navigate to chat (which uses default assistant for new users)
|
||||
@@ -470,7 +465,7 @@ test.describe("Default Assistant MCP Integration", () => {
|
||||
test.skip(!basicUserEmail, "Basic user must be created first");
|
||||
|
||||
await page.context().clearCookies();
|
||||
await loginWithCredentials(page, basicUserEmail, basicUserPassword);
|
||||
await apiLogin(page, basicUserEmail, basicUserPassword);
|
||||
|
||||
await page.goto("/app");
|
||||
await ensureOnboardingComplete(page);
|
||||
@@ -517,7 +512,7 @@ test.describe("Default Assistant MCP Integration", () => {
|
||||
const assistantId = assistantIdMatch ? assistantIdMatch[1] : null;
|
||||
expect(assistantId).not.toBeNull();
|
||||
|
||||
const client = new OnyxApiClient(page);
|
||||
const client = new OnyxApiClient(page.request);
|
||||
const assistant = await client.getAssistant(Number(assistantId));
|
||||
const hasMcpTool = assistant.tools.some(
|
||||
(tool) => tool.mcp_server_id === serverId
|
||||
@@ -665,7 +660,7 @@ test.describe("Default Assistant MCP Integration", () => {
|
||||
test.skip(!basicUserEmail, "Basic user must be created first");
|
||||
|
||||
await page.context().clearCookies();
|
||||
await loginWithCredentials(page, basicUserEmail, basicUserPassword);
|
||||
await apiLogin(page, basicUserEmail, basicUserPassword);
|
||||
console.log(`[test] Logged in as basic user to verify tool visibility`);
|
||||
|
||||
// Navigate to chat
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import type { Page, Browser, Locator } from "@playwright/test";
|
||||
import { loginAs, loginWithCredentials } from "../utils/auth";
|
||||
import { loginAs, apiLogin } from "../utils/auth";
|
||||
import { OnyxApiClient } from "../utils/onyxApiClient";
|
||||
import { startMcpOauthServer, McpServerProcess } from "../utils/mcpServer";
|
||||
import { TEST_ADMIN_CREDENTIALS } from "../constants";
|
||||
@@ -1056,7 +1056,7 @@ test.describe("MCP OAuth flows", () => {
|
||||
storageState: "admin_auth.json",
|
||||
});
|
||||
const adminPage = await adminContext.newPage();
|
||||
const adminClient = new OnyxApiClient(adminPage);
|
||||
const adminClient = new OnyxApiClient(adminPage.request);
|
||||
try {
|
||||
const existingServers = await adminClient.listMcpServers();
|
||||
for (const server of existingServers) {
|
||||
@@ -1128,7 +1128,7 @@ test.describe("MCP OAuth flows", () => {
|
||||
storageState: "admin_auth.json",
|
||||
});
|
||||
const adminPage = await adminContext.newPage();
|
||||
const adminClient = new OnyxApiClient(adminPage);
|
||||
const adminClient = new OnyxApiClient(adminPage.request);
|
||||
|
||||
if (adminArtifacts?.assistantId) {
|
||||
await adminClient.deleteAssistant(adminArtifacts.assistantId);
|
||||
@@ -1180,7 +1180,7 @@ test.describe("MCP OAuth flows", () => {
|
||||
{ email: TEST_ADMIN_CREDENTIALS.email, role: "admin" },
|
||||
"AdminFlow primary login"
|
||||
);
|
||||
const adminApiClient = new OnyxApiClient(page);
|
||||
const adminApiClient = new OnyxApiClient(page.request);
|
||||
logStep("Logged in as admin");
|
||||
|
||||
const serverName = `PW MCP Admin ${Date.now()}`;
|
||||
@@ -1432,7 +1432,7 @@ test.describe("MCP OAuth flows", () => {
|
||||
|
||||
await page.context().clearCookies();
|
||||
logStep("Cleared cookies");
|
||||
await loginWithCredentials(
|
||||
await apiLogin(
|
||||
page,
|
||||
curatorCredentials!.email,
|
||||
curatorCredentials!.password
|
||||
@@ -1443,7 +1443,7 @@ test.describe("MCP OAuth flows", () => {
|
||||
"CuratorFlow primary login"
|
||||
);
|
||||
logStep("Logged in as curator");
|
||||
const curatorApiClient = new OnyxApiClient(page);
|
||||
const curatorApiClient = new OnyxApiClient(page.request);
|
||||
|
||||
const serverName = `PW MCP Curator ${Date.now()}`;
|
||||
const assistantName = `PW Curator Assistant ${Date.now()}`;
|
||||
@@ -1654,7 +1654,7 @@ test.describe("MCP OAuth flows", () => {
|
||||
curatorTwoPage,
|
||||
"CuratorFlow secondary pre-login logout"
|
||||
);
|
||||
await loginWithCredentials(
|
||||
await apiLogin(
|
||||
curatorTwoPage,
|
||||
curatorTwoCredentials!.email,
|
||||
curatorTwoCredentials!.password
|
||||
|
||||
+43
-137
@@ -1,17 +1,34 @@
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import type { Page } from "@playwright/test";
|
||||
import {
|
||||
TEST_ADMIN2_CREDENTIALS,
|
||||
TEST_ADMIN_CREDENTIALS,
|
||||
TEST_USER_CREDENTIALS,
|
||||
} from "../constants";
|
||||
import { logPageState } from "./pageStateLogger";
|
||||
|
||||
// Logs in a user (admin, user, or admin2) to the application.
|
||||
/**
|
||||
* Log in via the API and set cookies on the page's browser context.
|
||||
* Much faster than navigating through the login UI.
|
||||
*/
|
||||
export async function apiLogin(
|
||||
page: Page,
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<void> {
|
||||
const res = await page.request.post("/api/auth/login", {
|
||||
form: { username: email, password },
|
||||
});
|
||||
if (!res.ok()) {
|
||||
const body = await res.text();
|
||||
throw new Error(`API login failed for ${email}: ${res.status()} ${body}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Logs in a known test user (admin, user, or admin2) via the API.
|
||||
// Users must already be provisioned (see global-setup.ts).
|
||||
export async function loginAs(
|
||||
page: Page,
|
||||
userType: "admin" | "user" | "admin2"
|
||||
) {
|
||||
): Promise<void> {
|
||||
const { email, password } =
|
||||
userType === "admin"
|
||||
? TEST_ADMIN_CREDENTIALS
|
||||
@@ -19,92 +36,10 @@ export async function loginAs(
|
||||
? TEST_ADMIN2_CREDENTIALS
|
||||
: TEST_USER_CREDENTIALS;
|
||||
|
||||
const waitForVisible = async (
|
||||
locator: Locator,
|
||||
debugContext: string,
|
||||
timeoutMs = 30000
|
||||
) => {
|
||||
try {
|
||||
await locator.waitFor({ state: "visible", timeout: timeoutMs });
|
||||
} catch (error) {
|
||||
await logPageState(page, debugContext, "[login-debug]");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const fillCredentials = async (contextLabel: string) => {
|
||||
const emailInput = page.getByTestId("email");
|
||||
const passwordInput = page.getByTestId("password");
|
||||
await waitForVisible(emailInput, `${contextLabel}: email input`);
|
||||
await waitForVisible(passwordInput, `${contextLabel}: password input`);
|
||||
await emailInput.fill(email);
|
||||
await passwordInput.fill(password);
|
||||
};
|
||||
|
||||
console.log(`[loginAs] Navigating to /auth/login as ${userType}`);
|
||||
await page.goto("/auth/login");
|
||||
|
||||
// Wait for navigation to settle (login page may redirect to signup if no users exist)
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
const currentUrl = page.url();
|
||||
const isOnSignup = currentUrl.includes("/auth/signup");
|
||||
console.log(
|
||||
`[loginAs] After navigation, landed on: ${currentUrl} (isOnSignup: ${isOnSignup})`
|
||||
);
|
||||
|
||||
await fillCredentials(
|
||||
isOnSignup ? "loginAs signup form" : "loginAs login form"
|
||||
);
|
||||
|
||||
// Click the submit button
|
||||
await page.click('button[type="submit"]');
|
||||
// Log any console errors during login
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
console.log(`[page:console:error] ${msg.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await page.waitForURL(/\/app.*/, { timeout: 10000 });
|
||||
console.log(
|
||||
`[loginAs] Redirected to /app for ${userType}. URL: ${page.url()}`
|
||||
);
|
||||
} catch {
|
||||
await logPageState(
|
||||
page,
|
||||
`[loginAs] Timeout waiting for /app redirect (${userType}). URL: ${page.url()}`,
|
||||
"[login-debug]"
|
||||
);
|
||||
throw new Error(
|
||||
`[loginAs] Failed to login as ${userType}. Current URL: ${page.url()}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to fetch current user info from the page context
|
||||
const me = await page.evaluate(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/me", { credentials: "include" });
|
||||
return {
|
||||
ok: res.ok,
|
||||
status: res.status,
|
||||
url: res.url,
|
||||
body: await res.text(),
|
||||
};
|
||||
} catch (e) {
|
||||
return { ok: false, status: 0, url: "", body: `error: ${String(e)}` };
|
||||
}
|
||||
});
|
||||
console.log(
|
||||
`[loginAs] /api/me => ok=${me.ok} status=${me.status} url=${me.url}`
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(`[loginAs] Failed to query /api/me: ${String(e)}`);
|
||||
}
|
||||
await apiLogin(page, email, password);
|
||||
}
|
||||
// Function to generate a random email and password
|
||||
|
||||
// Generate a random email and password for throwaway test users.
|
||||
const generateRandomCredentials = () => {
|
||||
const randomString = Math.random().toString(36).substring(2, 10);
|
||||
const specialChars = "!@#$%^&*()_+{}[]|:;<>,.?~";
|
||||
@@ -121,57 +56,28 @@ const generateRandomCredentials = () => {
|
||||
};
|
||||
};
|
||||
|
||||
// Function to sign up a new random user
|
||||
export async function loginAsRandomUser(page: Page) {
|
||||
// Register and log in as a new random user via the API.
|
||||
export async function loginAsRandomUser(page: Page): Promise<{
|
||||
email: string;
|
||||
password: string;
|
||||
}> {
|
||||
const { email, password } = generateRandomCredentials();
|
||||
|
||||
await page.goto("/auth/signup");
|
||||
|
||||
const emailInput = page.getByTestId("email");
|
||||
const passwordInput = page.getByTestId("password");
|
||||
await emailInput.waitFor({ state: "visible", timeout: 30000 });
|
||||
await emailInput.fill(email);
|
||||
await passwordInput.fill(password);
|
||||
|
||||
// Click the signup button
|
||||
await page.click('button[type="submit"]');
|
||||
try {
|
||||
await page.waitForURL(/\/(app|chat)(\?.*)?$/, { timeout: 30000 });
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
} catch {
|
||||
console.log(`Timeout occurred. Current URL: ${page.url()}`);
|
||||
throw new Error("Failed to sign up and redirect to app page");
|
||||
const registerRes = await page.request.post("/api/auth/register", {
|
||||
data: { email, username: email, password },
|
||||
});
|
||||
if (!registerRes.ok()) {
|
||||
const body = await registerRes.text();
|
||||
throw new Error(
|
||||
`Failed to register random user ${email}: ${registerRes.status()} ${body}`
|
||||
);
|
||||
}
|
||||
|
||||
await apiLogin(page, email, password);
|
||||
|
||||
// Navigate to the app so the page is ready for test interactions
|
||||
await page.goto("/app?new_team=true");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
return { email, password };
|
||||
}
|
||||
|
||||
export async function loginWithCredentials(
|
||||
page: Page,
|
||||
email: string,
|
||||
password: string
|
||||
) {
|
||||
if (process.env.SKIP_AUTH === "true") {
|
||||
console.log("[loginWithCredentials] Skipping authentication");
|
||||
return;
|
||||
}
|
||||
|
||||
await page.goto("/auth/login");
|
||||
|
||||
// Wait for navigation to settle (login page may redirect to signup if no users exist)
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
const currentUrl = page.url();
|
||||
const isOnSignup = currentUrl.includes("/auth/signup");
|
||||
console.log(
|
||||
`[loginWithCredentials] After navigation, landed on: ${currentUrl} (isOnSignup: ${isOnSignup})`
|
||||
);
|
||||
|
||||
const emailInput = page.getByTestId("email");
|
||||
const passwordInput = page.getByTestId("password");
|
||||
await emailInput.waitFor({ state: "visible", timeout: 30000 });
|
||||
await emailInput.fill(email);
|
||||
await passwordInput.fill(password);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(/\/app.*/, { timeout: 15000 });
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page, expect, APIResponse } from "@playwright/test";
|
||||
import { APIRequestContext, expect, APIResponse } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* API Client for Onyx backend operations in E2E tests.
|
||||
@@ -18,7 +18,10 @@ import { Page, expect, APIResponse } from "@playwright/test";
|
||||
* - `deleteDocumentSet(id)` - Deletes a document set (with polling until complete)
|
||||
*
|
||||
* **LLM Providers:**
|
||||
* - `listLlmProviders()` - Lists LLM providers (admin endpoint, includes is_public)
|
||||
* - `ensurePublicProvider(name?)` - Idempotently creates a public default LLM provider
|
||||
* - `createRestrictedProvider(name, groupId)` - Creates a restricted LLM provider assigned to a group
|
||||
* - `setProviderAsDefault(id)` - Sets an LLM provider as the default for chat
|
||||
* - `deleteProvider(id)` - Deletes an LLM provider
|
||||
*
|
||||
* **User Groups:**
|
||||
@@ -41,19 +44,33 @@ import { Page, expect, APIResponse } from "@playwright/test";
|
||||
*
|
||||
* **Usage Example:**
|
||||
* ```typescript
|
||||
* const client = new OnyxApiClient(page);
|
||||
* const { ccPairId } = await client.createFileConnector("Test Connector");
|
||||
* const docSetId = await client.createDocumentSet("Test Set", [ccPairId]);
|
||||
* await client.deleteDocumentSet(docSetId);
|
||||
* await client.deleteCCPair(ccPairId);
|
||||
* // From a test with a Page:
|
||||
* const client = new OnyxApiClient(page.request);
|
||||
*
|
||||
* // From global-setup with a standalone context (pass baseURL explicitly):
|
||||
* const ctx = await request.newContext({ baseURL, storageState: "admin_auth.json" });
|
||||
* const client = new OnyxApiClient(ctx, baseURL);
|
||||
* ```
|
||||
*
|
||||
* @param page - Playwright Page instance with authenticated session
|
||||
* @param request - Playwright APIRequestContext with authenticated session
|
||||
* (e.g. `page.request`, `context.request`, or `request.newContext()`)
|
||||
* @param baseUrl - Optional base URL override (e.g. `http://localhost:3000`).
|
||||
* Defaults to `process.env.BASE_URL` or `http://localhost:3000`.
|
||||
* Pass this when the Playwright-configured baseURL differs from
|
||||
* the env var (e.g. in `global-setup.ts` where the config value
|
||||
* is authoritative).
|
||||
*/
|
||||
export class OnyxApiClient {
|
||||
private baseUrl = `${process.env.BASE_URL || "http://localhost:3000"}/api`;
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(private page: Page) {}
|
||||
constructor(
|
||||
private request: APIRequestContext,
|
||||
baseUrl?: string
|
||||
) {
|
||||
this.baseUrl = `${
|
||||
baseUrl ?? process.env.BASE_URL ?? "http://localhost:3000"
|
||||
}/api`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic GET request to the API.
|
||||
@@ -62,7 +79,7 @@ export class OnyxApiClient {
|
||||
* @returns The API response
|
||||
*/
|
||||
private async get(endpoint: string): Promise<APIResponse> {
|
||||
return await this.page.request.get(`${this.baseUrl}${endpoint}`);
|
||||
return await this.request.get(`${this.baseUrl}${endpoint}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,7 +90,7 @@ export class OnyxApiClient {
|
||||
* @returns The API response
|
||||
*/
|
||||
private async post(endpoint: string, data?: any): Promise<APIResponse> {
|
||||
return await this.page.request.post(`${this.baseUrl}${endpoint}`, {
|
||||
return await this.request.post(`${this.baseUrl}${endpoint}`, {
|
||||
data,
|
||||
});
|
||||
}
|
||||
@@ -85,7 +102,7 @@ export class OnyxApiClient {
|
||||
* @returns The API response
|
||||
*/
|
||||
private async delete(endpoint: string): Promise<APIResponse> {
|
||||
return await this.page.request.delete(`${this.baseUrl}${endpoint}`);
|
||||
return await this.request.delete(`${this.baseUrl}${endpoint}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +113,7 @@ export class OnyxApiClient {
|
||||
* @returns The API response
|
||||
*/
|
||||
private async put(endpoint: string, data?: any): Promise<APIResponse> {
|
||||
return await this.page.request.put(`${this.baseUrl}${endpoint}`, {
|
||||
return await this.request.put(`${this.baseUrl}${endpoint}`, {
|
||||
data,
|
||||
});
|
||||
}
|
||||
@@ -371,7 +388,7 @@ export class OnyxApiClient {
|
||||
providerName: string,
|
||||
groupId: number
|
||||
): Promise<number> {
|
||||
const response = await this.page.request.put(
|
||||
const response = await this.request.put(
|
||||
`${this.baseUrl}/admin/llm/provider?is_creation=true`,
|
||||
{
|
||||
data: {
|
||||
@@ -397,18 +414,41 @@ export class OnyxApiClient {
|
||||
return responseData.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists LLM providers visible to the admin (includes `is_public`).
|
||||
*
|
||||
* @returns Array of LLM providers with id and is_public fields
|
||||
*/
|
||||
async listLlmProviders(): Promise<
|
||||
Array<{
|
||||
id: number;
|
||||
is_public?: boolean;
|
||||
}>
|
||||
> {
|
||||
const response = await this.get("/llm/provider");
|
||||
const response = await this.get("/admin/llm/provider");
|
||||
return await this.handleResponse(response, "Failed to list LLM providers");
|
||||
}
|
||||
|
||||
async createPublicProvider(providerName: string): Promise<number> {
|
||||
const response = await this.page.request.put(
|
||||
/**
|
||||
* Ensure at least one public LLM provider exists and is set as default.
|
||||
*
|
||||
* Idempotent — returns `null` if a public provider already exists,
|
||||
* or the new provider ID if one was created.
|
||||
*
|
||||
* @param providerName - Name for the provider (default: "PW Default Provider")
|
||||
* @returns The provider ID if one was created, or `null` if already present
|
||||
*/
|
||||
async ensurePublicProvider(
|
||||
providerName: string = "PW Default Provider"
|
||||
): Promise<number | null> {
|
||||
const providers = await this.listLlmProviders();
|
||||
const hasPublic = providers.some((p) => p.is_public);
|
||||
|
||||
if (hasPublic) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = await this.request.put(
|
||||
`${this.baseUrl}/admin/llm/provider?is_creation=true`,
|
||||
{
|
||||
data: {
|
||||
@@ -428,12 +468,33 @@ export class OnyxApiClient {
|
||||
"Failed to create public provider"
|
||||
);
|
||||
|
||||
// Set as default so get_default_llm() works (needed for tokenization, etc.)
|
||||
await this.setProviderAsDefault(responseData.id);
|
||||
|
||||
this.log(
|
||||
`Created public LLM provider: ${providerName} (ID: ${responseData.id})`
|
||||
);
|
||||
return responseData.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an LLM provider as the default for chat.
|
||||
*
|
||||
* @param providerId - The provider ID to set as default
|
||||
*/
|
||||
async setProviderAsDefault(providerId: number): Promise<void> {
|
||||
const response = await this.post(
|
||||
`/admin/llm/provider/${providerId}/default`
|
||||
);
|
||||
|
||||
await this.handleResponseSoft(
|
||||
response,
|
||||
`Failed to set provider ${providerId} as default`
|
||||
);
|
||||
|
||||
this.log(`Set LLM provider ${providerId} as default`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an LLM provider.
|
||||
*
|
||||
@@ -497,7 +558,7 @@ export class OnyxApiClient {
|
||||
role: "admin" | "curator" | "global_curator" | "basic",
|
||||
explicitOverride = false
|
||||
): Promise<void> {
|
||||
const response = await this.page.request.patch(
|
||||
const response = await this.request.patch(
|
||||
`${this.baseUrl}/manage/set-user-role`,
|
||||
{
|
||||
data: {
|
||||
@@ -512,7 +573,7 @@ export class OnyxApiClient {
|
||||
}
|
||||
|
||||
async deleteMcpServer(serverId: number): Promise<boolean> {
|
||||
const response = await this.page.request.delete(
|
||||
const response = await this.request.delete(
|
||||
`${this.baseUrl}/admin/mcp/server/${serverId}`
|
||||
);
|
||||
const success = await this.handleResponseSoft(
|
||||
@@ -526,7 +587,7 @@ export class OnyxApiClient {
|
||||
}
|
||||
|
||||
async deleteAssistant(assistantId: number): Promise<boolean> {
|
||||
const response = await this.page.request.delete(
|
||||
const response = await this.request.delete(
|
||||
`${this.baseUrl}/persona/${assistantId}`
|
||||
);
|
||||
const success = await this.handleResponseSoft(
|
||||
@@ -589,16 +650,13 @@ export class OnyxApiClient {
|
||||
}
|
||||
|
||||
async registerUser(email: string, password: string): Promise<{ id: string }> {
|
||||
const response = await this.page.request.post(
|
||||
`${this.baseUrl}/auth/register`,
|
||||
{
|
||||
data: {
|
||||
email,
|
||||
username: email,
|
||||
password,
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await this.request.post(`${this.baseUrl}/auth/register`, {
|
||||
data: {
|
||||
email,
|
||||
username: email,
|
||||
password,
|
||||
},
|
||||
});
|
||||
const data = await this.handleResponse<{ id: string }>(
|
||||
response,
|
||||
`Failed to register user ${email}`
|
||||
@@ -611,7 +669,7 @@ export class OnyxApiClient {
|
||||
email: string;
|
||||
role: string;
|
||||
} | null> {
|
||||
const response = await this.page.request.get(
|
||||
const response = await this.request.get(
|
||||
`${this.baseUrl}/manage/users/accepted`,
|
||||
{
|
||||
params: {
|
||||
@@ -639,7 +697,7 @@ export class OnyxApiClient {
|
||||
userId: string,
|
||||
isCurator: boolean = true
|
||||
): Promise<void> {
|
||||
const response = await this.page.request.post(
|
||||
const response = await this.request.post(
|
||||
`${this.baseUrl}/manage/admin/user-group/${userGroupId}/set-curator`,
|
||||
{
|
||||
data: {
|
||||
@@ -851,7 +909,7 @@ export class OnyxApiClient {
|
||||
guild_name: string | null;
|
||||
enabled: boolean;
|
||||
}> {
|
||||
const response = await this.page.request.patch(
|
||||
const response = await this.request.patch(
|
||||
`${this.baseUrl}/manage/admin/discord-bot/guilds/${guildId}`,
|
||||
{ data: updates }
|
||||
);
|
||||
@@ -926,7 +984,7 @@ export class OnyxApiClient {
|
||||
channel_name: string;
|
||||
enabled: boolean;
|
||||
}> {
|
||||
const response = await this.page.request.patch(
|
||||
const response = await this.request.patch(
|
||||
`${this.baseUrl}/manage/admin/discord-bot/guilds/${guildConfigId}/channels/${channelConfigId}`,
|
||||
{ data: updates }
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user