mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-06 16:53:38 +02:00
Add some test coverage
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
createFanoutConfig,
|
||||
deleteFanoutConfig,
|
||||
getFanoutConfigs,
|
||||
} from '../helpers/api';
|
||||
|
||||
test.describe('Apprise integration settings', () => {
|
||||
let createdAppriseId: string | null = null;
|
||||
|
||||
test.afterEach(async () => {
|
||||
if (createdAppriseId) {
|
||||
try {
|
||||
await deleteFanoutConfig(createdAppriseId);
|
||||
} catch {
|
||||
console.warn('Failed to delete test apprise config');
|
||||
}
|
||||
createdAppriseId = null;
|
||||
}
|
||||
});
|
||||
|
||||
test('create apprise via UI, configure URLs, save as enabled', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
// Open settings and navigate to MQTT & Forwarding
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
// Click the Apprise add button
|
||||
await page.getByRole('button', { name: 'Apprise' }).click();
|
||||
|
||||
// Should navigate to the detail/edit view with default name
|
||||
await expect(page.getByDisplayValue('Apprise')).toBeVisible();
|
||||
|
||||
// Fill in notification URL
|
||||
const urlsTextarea = page.locator('#fanout-apprise-urls');
|
||||
await urlsTextarea.fill('json://localhost:9999');
|
||||
|
||||
// Verify preserve identity checkbox is checked by default
|
||||
const preserveIdentity = page.getByText('Preserve identity on Discord');
|
||||
await expect(preserveIdentity).toBeVisible();
|
||||
|
||||
// Verify include routing path checkbox is checked by default
|
||||
const includePath = page.getByText('Include routing path in notifications');
|
||||
await expect(includePath).toBeVisible();
|
||||
|
||||
// Rename it
|
||||
const nameInput = page.locator('#fanout-edit-name');
|
||||
await nameInput.clear();
|
||||
await nameInput.fill('E2E Apprise');
|
||||
|
||||
// Save as enabled
|
||||
await page.getByRole('button', { name: /Save as Enabled/i }).click();
|
||||
await expect(page.getByText('Integration saved and enabled')).toBeVisible();
|
||||
|
||||
// Should be back on list view with our apprise config visible
|
||||
await expect(page.getByText('E2E Apprise')).toBeVisible();
|
||||
|
||||
// Clean up via API
|
||||
const configs = await getFanoutConfigs();
|
||||
const apprise = configs.find((c) => c.name === 'E2E Apprise');
|
||||
if (apprise) {
|
||||
createdAppriseId = apprise.id;
|
||||
}
|
||||
});
|
||||
|
||||
test('create apprise via API, verify options persist after edit', async ({ page }) => {
|
||||
const apprise = await createFanoutConfig({
|
||||
type: 'apprise',
|
||||
name: 'API Apprise',
|
||||
config: {
|
||||
urls: 'json://localhost:9999\nslack://token_a/token_b/token_c',
|
||||
preserve_identity: false,
|
||||
include_path: false,
|
||||
},
|
||||
enabled: true,
|
||||
});
|
||||
createdAppriseId = apprise.id;
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
// Click Edit on our apprise config
|
||||
const row = page.getByText('API Apprise').locator('..');
|
||||
await row.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// Verify the URLs textarea has our content
|
||||
const urlsTextarea = page.locator('#fanout-apprise-urls');
|
||||
await expect(urlsTextarea).toHaveValue(/json:\/\/localhost:9999/);
|
||||
await expect(urlsTextarea).toHaveValue(/slack:\/\/token_a/);
|
||||
|
||||
// Verify checkboxes reflect our config (both unchecked)
|
||||
const preserveCheckbox = page
|
||||
.getByText('Preserve identity on Discord')
|
||||
.locator('xpath=ancestor::label[1]')
|
||||
.locator('input[type="checkbox"]');
|
||||
await expect(preserveCheckbox).not.toBeChecked();
|
||||
|
||||
const pathCheckbox = page
|
||||
.getByText('Include routing path in notifications')
|
||||
.locator('xpath=ancestor::label[1]')
|
||||
.locator('input[type="checkbox"]');
|
||||
await expect(pathCheckbox).not.toBeChecked();
|
||||
|
||||
// Go back
|
||||
await page.getByText('← Back to list').click();
|
||||
});
|
||||
|
||||
test('apprise shows scope selector', async ({ page }) => {
|
||||
const apprise = await createFanoutConfig({
|
||||
type: 'apprise',
|
||||
name: 'Scope Apprise',
|
||||
config: { urls: 'json://localhost:9999' },
|
||||
});
|
||||
createdAppriseId = apprise.id;
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
const row = page.getByText('Scope Apprise').locator('..');
|
||||
await row.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// Verify scope selector is present
|
||||
await expect(page.getByText('Message Scope')).toBeVisible();
|
||||
await expect(page.getByText('All messages')).toBeVisible();
|
||||
|
||||
// Select "All except listed" mode
|
||||
await page.getByText('All except listed channels/contacts').click();
|
||||
|
||||
// Should show channel and contact lists with exclude label
|
||||
await expect(page.getByText('(exclude)')).toBeVisible();
|
||||
|
||||
// Go back
|
||||
await page.getByText('← Back to list').click();
|
||||
});
|
||||
|
||||
test('apprise disabled config shows amber dot and can be enabled via save button', async ({
|
||||
page,
|
||||
}) => {
|
||||
const apprise = await createFanoutConfig({
|
||||
type: 'apprise',
|
||||
name: 'Disabled Apprise',
|
||||
config: { urls: 'json://localhost:9999' },
|
||||
enabled: false,
|
||||
});
|
||||
createdAppriseId = apprise.id;
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
// Should show "Disabled" text
|
||||
const row = page.getByText('Disabled Apprise').locator('..');
|
||||
await expect(row.getByText('Disabled')).toBeVisible();
|
||||
|
||||
// Edit it
|
||||
await row.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// Save as enabled
|
||||
await page.getByRole('button', { name: /Save as Enabled/i }).click();
|
||||
await expect(page.getByText('Integration saved and enabled')).toBeVisible();
|
||||
|
||||
// Verify it's now enabled via API
|
||||
const configs = await getFanoutConfigs();
|
||||
const updated = configs.find((c) => c.id === apprise.id);
|
||||
expect(updated?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
test('delete apprise via UI', async ({ page }) => {
|
||||
const apprise = await createFanoutConfig({
|
||||
type: 'apprise',
|
||||
name: 'Delete Me Apprise',
|
||||
config: { urls: 'json://localhost:9999' },
|
||||
});
|
||||
createdAppriseId = apprise.id;
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
const row = page.getByText('Delete Me Apprise').locator('..');
|
||||
await row.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// Accept the confirmation dialog
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
await expect(page.getByText('Integration deleted')).toBeVisible();
|
||||
|
||||
// Should be back on list, apprise gone
|
||||
await expect(page.getByText('Delete Me Apprise')).not.toBeVisible();
|
||||
createdAppriseId = null;
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,9 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
ensureFlightlessChannel,
|
||||
getFanoutConfigs,
|
||||
createFanoutConfig,
|
||||
deleteFanoutConfig,
|
||||
updateFanoutConfig,
|
||||
} from '../helpers/api';
|
||||
import type { FanoutConfig } from '../helpers/api';
|
||||
|
||||
const BOT_CODE = `def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path):
|
||||
if channel_name == "#flightless" and "!e2etest" in message_text.lower():
|
||||
@@ -48,7 +45,7 @@ test.describe('Bot functionality', () => {
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /Fanout/ }).click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
// The bot name should be visible in the integration list
|
||||
await expect(page.getByText('E2E Test Bot')).toBeVisible();
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
createFanoutConfig,
|
||||
deleteFanoutConfig,
|
||||
getFanoutConfigs,
|
||||
} from '../helpers/api';
|
||||
|
||||
test.describe('Webhook integration settings', () => {
|
||||
let createdWebhookId: string | null = null;
|
||||
|
||||
test.afterEach(async () => {
|
||||
if (createdWebhookId) {
|
||||
try {
|
||||
await deleteFanoutConfig(createdWebhookId);
|
||||
} catch {
|
||||
console.warn('Failed to delete test webhook');
|
||||
}
|
||||
createdWebhookId = null;
|
||||
}
|
||||
});
|
||||
|
||||
test('create webhook via UI, configure, save as enabled, verify in list', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
// Open settings and navigate to MQTT & Forwarding
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
// Click the Webhook add button
|
||||
await page.getByRole('button', { name: 'Webhook' }).click();
|
||||
|
||||
// Should navigate to the detail/edit view with default name
|
||||
await expect(page.getByDisplayValue('Webhook')).toBeVisible();
|
||||
|
||||
// Fill in webhook URL
|
||||
const urlInput = page.locator('#fanout-webhook-url');
|
||||
await urlInput.fill('https://example.com/e2e-test-hook');
|
||||
|
||||
// Verify method defaults to POST
|
||||
await expect(page.locator('#fanout-webhook-method')).toHaveValue('POST');
|
||||
|
||||
// Fill in a secret
|
||||
const secretInput = page.locator('#fanout-webhook-secret');
|
||||
await secretInput.fill('e2e-secret');
|
||||
|
||||
// Rename it
|
||||
const nameInput = page.locator('#fanout-edit-name');
|
||||
await nameInput.clear();
|
||||
await nameInput.fill('E2E Webhook');
|
||||
|
||||
// Save as enabled
|
||||
await page.getByRole('button', { name: /Save as Enabled/i }).click();
|
||||
await expect(page.getByText('Integration saved and enabled')).toBeVisible();
|
||||
|
||||
// Should be back on list view with our webhook visible
|
||||
await expect(page.getByText('E2E Webhook')).toBeVisible();
|
||||
|
||||
// Clean up via API
|
||||
const configs = await getFanoutConfigs();
|
||||
const webhook = configs.find((c) => c.name === 'E2E Webhook');
|
||||
if (webhook) {
|
||||
createdWebhookId = webhook.id;
|
||||
}
|
||||
});
|
||||
|
||||
test('create webhook via API, edit in UI, save as disabled', async ({ page }) => {
|
||||
// Create via API
|
||||
const webhook = await createFanoutConfig({
|
||||
type: 'webhook',
|
||||
name: 'API Webhook',
|
||||
config: { url: 'https://example.com/hook', method: 'POST', headers: {}, secret: '' },
|
||||
enabled: true,
|
||||
});
|
||||
createdWebhookId = webhook.id;
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
// Click Edit on our webhook
|
||||
const row = page.getByText('API Webhook').locator('..');
|
||||
await row.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// Should be in edit view
|
||||
await expect(page.getByDisplayValue('API Webhook')).toBeVisible();
|
||||
|
||||
// Change method to PUT
|
||||
await page.locator('#fanout-webhook-method').selectOption('PUT');
|
||||
|
||||
// Save as disabled
|
||||
await page.getByRole('button', { name: /Save as Disabled/i }).click();
|
||||
await expect(page.getByText('Integration saved')).toBeVisible();
|
||||
|
||||
// Verify it's now disabled in the list
|
||||
const configs = await getFanoutConfigs();
|
||||
const updated = configs.find((c) => c.id === webhook.id);
|
||||
expect(updated?.enabled).toBe(false);
|
||||
expect(updated?.config.method).toBe('PUT');
|
||||
});
|
||||
|
||||
test('webhook shows scope selector with channel/contact options', async ({ page }) => {
|
||||
const webhook = await createFanoutConfig({
|
||||
type: 'webhook',
|
||||
name: 'Scope Webhook',
|
||||
config: { url: 'https://example.com/hook', method: 'POST', headers: {}, secret: '' },
|
||||
});
|
||||
createdWebhookId = webhook.id;
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
// Click Edit
|
||||
const row = page.getByText('Scope Webhook').locator('..');
|
||||
await row.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// Verify scope selector is visible with all four modes
|
||||
await expect(page.getByText('Message Scope')).toBeVisible();
|
||||
await expect(page.getByText('All messages')).toBeVisible();
|
||||
await expect(page.getByText('No messages')).toBeVisible();
|
||||
await expect(page.getByText('Only listed channels/contacts')).toBeVisible();
|
||||
await expect(page.getByText('All except listed channels/contacts')).toBeVisible();
|
||||
|
||||
// Select "Only listed" to see channel/contact checkboxes
|
||||
await page.getByText('Only listed channels/contacts').click();
|
||||
|
||||
// Should show Channels and Contacts sections
|
||||
await expect(page.getByText('Channels')).toBeVisible();
|
||||
await expect(page.getByText('Contacts')).toBeVisible();
|
||||
|
||||
// Go back without saving
|
||||
await page.getByText('← Back to list').click();
|
||||
});
|
||||
|
||||
test('delete webhook via UI', async ({ page }) => {
|
||||
const webhook = await createFanoutConfig({
|
||||
type: 'webhook',
|
||||
name: 'Delete Me Webhook',
|
||||
config: { url: 'https://example.com/hook', method: 'POST', headers: {}, secret: '' },
|
||||
});
|
||||
createdWebhookId = webhook.id;
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText('Settings').click();
|
||||
await page.getByRole('button', { name: /MQTT.*Forwarding/ }).click();
|
||||
|
||||
// Click Edit
|
||||
const row = page.getByText('Delete Me Webhook').locator('..');
|
||||
await row.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// Accept the confirmation dialog
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
|
||||
// Click Delete
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
|
||||
await expect(page.getByText('Integration deleted')).toBeVisible();
|
||||
|
||||
// Should be back on list, webhook gone
|
||||
await expect(page.getByText('Delete Me Webhook')).not.toBeVisible();
|
||||
|
||||
// Already deleted, clear the cleanup reference
|
||||
createdWebhookId = null;
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user