Adding Audit and Webhooks
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import WebhookDeliveryHistory from '@/components/settings/WebhookDeliveryHistory.vue';
|
||||
import type { WebhookDeliveryLogResponse } from '@/types/api';
|
||||
|
||||
jest.mock('@/api/webhooks');
|
||||
jest.mock('@/api/client', () => ({
|
||||
setAuthTokens: jest.fn(),
|
||||
clearAuthTokens: jest.fn(),
|
||||
getAccessToken: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as webhooksApi from '@/api/webhooks';
|
||||
|
||||
const mockDeliveries: WebhookDeliveryLogResponse[] = [
|
||||
{
|
||||
id: 1, webhookId: 10, eventType: 'feature.updated', success: true,
|
||||
responseStatusCode: 200, responseBody: 'OK', errorMessage: null,
|
||||
attemptNumber: 1, attemptedAt: '2026-01-10T10:00:00Z', duration: '00:00:00.123',
|
||||
},
|
||||
{
|
||||
id: 2, webhookId: 10, eventType: 'feature.created', success: false,
|
||||
responseStatusCode: 500, responseBody: null, errorMessage: 'Internal Server Error',
|
||||
attemptNumber: 3, attemptedAt: '2026-01-11T11:00:00Z', duration: '00:00:01.500',
|
||||
},
|
||||
];
|
||||
|
||||
function mountComponent(props: Record<string, unknown> = {}) {
|
||||
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/', component: { template: '<div />' } }] });
|
||||
return mount(WebhookDeliveryHistory, {
|
||||
props: { envApiKey: 'env-dev', webhookId: 10, ...props },
|
||||
global: { plugins: [router] },
|
||||
});
|
||||
}
|
||||
|
||||
describe('WebhookDeliveryHistory', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('WhenMounted', () => {
|
||||
it('ThenDeliveriesAreLoaded', async () => {
|
||||
jest.mocked(webhooksApi.listWebhookDeliveries).mockResolvedValue(mockDeliveries);
|
||||
|
||||
mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(webhooksApi.listWebhookDeliveries).toHaveBeenCalledWith('env-dev', 10);
|
||||
});
|
||||
|
||||
it('ThenNoDeliveriesMessageIsShownWhenEmpty', async () => {
|
||||
jest.mocked(webhooksApi.listWebhookDeliveries).mockResolvedValue([]);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="no-deliveries-message"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenDeliveriesTableIsRendered', async () => {
|
||||
jest.mocked(webhooksApi.listWebhookDeliveries).mockResolvedValue(mockDeliveries);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="deliveries-table"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="delivery-row-1"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="delivery-row-2"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenRenderingDeliveryStatuses', () => {
|
||||
it('ThenSuccessfulDeliveryShowsSuccessChip', async () => {
|
||||
jest.mocked(webhooksApi.listWebhookDeliveries).mockResolvedValue(mockDeliveries);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
const successChip = wrapper.find('[data-testid="delivery-status-1"]');
|
||||
expect(successChip.exists()).toBe(true);
|
||||
expect(successChip.text()).toContain('Success');
|
||||
});
|
||||
|
||||
it('ThenFailedDeliveryShowsFailedChip', async () => {
|
||||
jest.mocked(webhooksApi.listWebhookDeliveries).mockResolvedValue(mockDeliveries);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
const failedChip = wrapper.find('[data-testid="delivery-status-2"]');
|
||||
expect(failedChip.exists()).toBe(true);
|
||||
expect(failedChip.text()).toContain('Failed');
|
||||
});
|
||||
|
||||
it('ThenEventTypesAreDisplayed', async () => {
|
||||
jest.mocked(webhooksApi.listWebhookDeliveries).mockResolvedValue(mockDeliveries);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.text()).toContain('feature.updated');
|
||||
expect(wrapper.text()).toContain('feature.created');
|
||||
});
|
||||
|
||||
it('ThenAttemptNumbersAreDisplayed', async () => {
|
||||
jest.mocked(webhooksApi.listWebhookDeliveries).mockResolvedValue(mockDeliveries);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.text()).toContain('3'); // attemptNumber of second delivery
|
||||
});
|
||||
});
|
||||
});
|
||||
131
src/admin/tests/unit/components/settings/WebhookDialog.spec.ts
Normal file
131
src/admin/tests/unit/components/settings/WebhookDialog.spec.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { mount, flushPromises, type VueWrapper } from '@vue/test-utils';
|
||||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import WebhookDialog from '@/components/settings/WebhookDialog.vue';
|
||||
import type { WebhookResponse } from '@/types/api';
|
||||
|
||||
jest.mock('@/api/webhooks');
|
||||
jest.mock('@/api/client', () => ({
|
||||
setAuthTokens: jest.fn(),
|
||||
clearAuthTokens: jest.fn(),
|
||||
getAccessToken: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as webhooksApi from '@/api/webhooks';
|
||||
|
||||
const mockWebhook: WebhookResponse = {
|
||||
id: 1, url: 'https://example.com/hook', secret: 'mysecret', scope: 'Organization',
|
||||
enabled: true, environmentId: null, organizationId: 1, createdAt: '2026-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const dialogStub = { template: '<div><slot /></div>' };
|
||||
|
||||
function mountDialog(props: Record<string, unknown> = {}) {
|
||||
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/', component: { template: '<div />' } }] });
|
||||
return mount(WebhookDialog, {
|
||||
props: { modelValue: true, orgId: 1, ...props },
|
||||
global: { plugins: [router], stubs: { 'v-dialog': dialogStub } },
|
||||
});
|
||||
}
|
||||
|
||||
describe('WebhookDialog', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('WhenOpenedForCreate', () => {
|
||||
it('ThenDialogIsRendered', () => {
|
||||
const wrapper = mountDialog();
|
||||
expect(wrapper.find('[data-testid="webhook-dialog"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenTitleShowsAddWebhook', () => {
|
||||
const wrapper = mountDialog();
|
||||
expect(wrapper.text()).toContain('Add Webhook');
|
||||
});
|
||||
|
||||
it('ThenCreateOrgWebhookIsCalledOnSave', async () => {
|
||||
const created = { ...mockWebhook, id: 99 };
|
||||
jest.mocked(webhooksApi.createOrgWebhook).mockResolvedValue(created);
|
||||
|
||||
const wrapper = mountDialog();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="webhook-url-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'https://example.com/new');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="save-webhook-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(webhooksApi.createOrgWebhook).toHaveBeenCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ url: 'https://example.com/new' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('ThenSavedEventIsEmitted', async () => {
|
||||
const created = { ...mockWebhook, id: 99 };
|
||||
jest.mocked(webhooksApi.createOrgWebhook).mockResolvedValue(created);
|
||||
|
||||
const wrapper = mountDialog();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="webhook-url-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'https://example.com/new');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="save-webhook-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.emitted('saved')).toBeTruthy();
|
||||
expect(wrapper.emitted('saved')![0]).toEqual([created]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenOpenedForEdit', () => {
|
||||
it('ThenTitleShowsEditWebhook', () => {
|
||||
const wrapper = mountDialog({ webhook: mockWebhook });
|
||||
expect(wrapper.text()).toContain('Edit Webhook');
|
||||
});
|
||||
|
||||
it('ThenFormIsPrePopulatedWithWebhookData', () => {
|
||||
const wrapper = mountDialog({ webhook: mockWebhook });
|
||||
const urlInput = wrapper.findComponent('[data-testid="webhook-url-input"]') as VueWrapper<any>;
|
||||
expect(urlInput.props('modelValue')).toBe(mockWebhook.url);
|
||||
});
|
||||
|
||||
it('ThenUpdateOrgWebhookIsCalledOnSave', async () => {
|
||||
const updated = { ...mockWebhook, url: 'https://example.com/updated' };
|
||||
jest.mocked(webhooksApi.updateOrgWebhook).mockResolvedValue(updated);
|
||||
|
||||
const wrapper = mountDialog({ webhook: mockWebhook });
|
||||
|
||||
await wrapper.find('[data-testid="save-webhook-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(webhooksApi.updateOrgWebhook).toHaveBeenCalledWith(
|
||||
1, mockWebhook.id,
|
||||
expect.objectContaining({ url: mockWebhook.url }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenEnvScopedDialogIsUsed', () => {
|
||||
it('ThenCreateEnvWebhookIsCalledOnSave', async () => {
|
||||
const created = { ...mockWebhook, id: 99, scope: 'Environment' as const };
|
||||
jest.mocked(webhooksApi.createEnvWebhook).mockResolvedValue(created);
|
||||
|
||||
const wrapper = mountDialog({ orgId: undefined, envApiKey: 'env-dev' });
|
||||
|
||||
await (wrapper.findComponent('[data-testid="webhook-url-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'https://example.com/new');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="save-webhook-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(webhooksApi.createEnvWebhook).toHaveBeenCalledWith(
|
||||
'env-dev',
|
||||
expect.objectContaining({ url: 'https://example.com/new' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
149
src/admin/tests/unit/components/settings/WebhooksPanel.spec.ts
Normal file
149
src/admin/tests/unit/components/settings/WebhooksPanel.spec.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { mount, flushPromises, type VueWrapper } from '@vue/test-utils';
|
||||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import WebhooksPanel from '@/components/settings/WebhooksPanel.vue';
|
||||
import type { WebhookResponse } from '@/types/api';
|
||||
|
||||
jest.mock('@/api/webhooks');
|
||||
jest.mock('@/api/client', () => ({
|
||||
setAuthTokens: jest.fn(),
|
||||
clearAuthTokens: jest.fn(),
|
||||
getAccessToken: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as webhooksApi from '@/api/webhooks';
|
||||
|
||||
const mockOrgWebhooks: WebhookResponse[] = [
|
||||
{
|
||||
id: 1, url: 'https://example.com/hook1', secret: null, scope: 'Organization',
|
||||
enabled: true, environmentId: null, organizationId: 1, createdAt: '2026-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 2, url: 'https://example.com/hook2', secret: 'abc', scope: 'Organization',
|
||||
enabled: false, environmentId: null, organizationId: 1, createdAt: '2026-01-02T00:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
const dialogStub = { template: '<div><slot /></div>' };
|
||||
|
||||
function mountPanel(props: Record<string, unknown> = {}) {
|
||||
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/', component: { template: '<div />' } }] });
|
||||
return mount(WebhooksPanel, {
|
||||
props: { orgId: 1, ...props },
|
||||
global: { plugins: [router], stubs: { 'v-dialog': dialogStub } },
|
||||
});
|
||||
}
|
||||
|
||||
describe('WebhooksPanel', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('WhenMountedWithOrgScope', () => {
|
||||
it('ThenOrgWebhooksAreLoaded', async () => {
|
||||
jest.mocked(webhooksApi.listOrgWebhooks).mockResolvedValue(mockOrgWebhooks);
|
||||
|
||||
mountPanel();
|
||||
await flushPromises();
|
||||
|
||||
expect(webhooksApi.listOrgWebhooks).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('ThenWebhooksTableIsRendered', async () => {
|
||||
jest.mocked(webhooksApi.listOrgWebhooks).mockResolvedValue(mockOrgWebhooks);
|
||||
|
||||
const wrapper = mountPanel();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="webhooks-table"]').exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('https://example.com/hook1');
|
||||
});
|
||||
|
||||
it('ThenNoWebhooksMessageIsShownWhenEmpty', async () => {
|
||||
jest.mocked(webhooksApi.listOrgWebhooks).mockResolvedValue([]);
|
||||
|
||||
const wrapper = mountPanel();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="no-webhooks-message"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenMountedWithEnvScope', () => {
|
||||
it('ThenEnvWebhooksAreLoaded', async () => {
|
||||
jest.mocked(webhooksApi.listEnvWebhooks).mockResolvedValue([]);
|
||||
|
||||
mountPanel({ orgId: undefined, envApiKey: 'env-dev' });
|
||||
await flushPromises();
|
||||
|
||||
expect(webhooksApi.listEnvWebhooks).toHaveBeenCalledWith('env-dev');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenEnabledToggleIsChanged', () => {
|
||||
it('ThenUpdateOrgWebhookIsCalledWithToggledEnabled', async () => {
|
||||
jest.mocked(webhooksApi.listOrgWebhooks).mockResolvedValue(mockOrgWebhooks);
|
||||
jest.mocked(webhooksApi.updateOrgWebhook).mockResolvedValue({ ...mockOrgWebhooks[0], enabled: false });
|
||||
|
||||
const wrapper = mountPanel();
|
||||
await flushPromises();
|
||||
|
||||
await (wrapper.findComponent(`[data-testid="webhook-enabled-toggle-1"]`) as VueWrapper<any>).vm.$emit('update:modelValue', false);
|
||||
await flushPromises();
|
||||
|
||||
expect(webhooksApi.updateOrgWebhook).toHaveBeenCalledWith(
|
||||
1, 1,
|
||||
expect.objectContaining({ enabled: false }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenDeleteIsConfirmed', () => {
|
||||
it('ThenDeleteOrgWebhookIsCalledAndWebhookIsRemoved', async () => {
|
||||
jest.mocked(webhooksApi.listOrgWebhooks).mockResolvedValue([...mockOrgWebhooks]);
|
||||
jest.mocked(webhooksApi.deleteOrgWebhook).mockResolvedValue(undefined);
|
||||
|
||||
const wrapper = mountPanel();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="delete-webhook-1"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="confirm-delete-webhook-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(webhooksApi.deleteOrgWebhook).toHaveBeenCalledWith(1, 1);
|
||||
expect(wrapper.text()).not.toContain('https://example.com/hook1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenWebhookIsSaved', () => {
|
||||
it('ThenNewWebhookIsAddedToList', async () => {
|
||||
jest.mocked(webhooksApi.listOrgWebhooks).mockResolvedValue([]);
|
||||
|
||||
const wrapper = mountPanel();
|
||||
await flushPromises();
|
||||
|
||||
const newHook: WebhookResponse = { ...mockOrgWebhooks[0], id: 99, url: 'https://new.example.com/hook' };
|
||||
await wrapper.findComponent({ name: 'WebhookDialog' }).vm.$emit('saved', newHook);
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.text()).toContain('https://new.example.com/hook');
|
||||
});
|
||||
|
||||
it('ThenExistingWebhookIsUpdatedInList', async () => {
|
||||
jest.mocked(webhooksApi.listOrgWebhooks).mockResolvedValue([...mockOrgWebhooks]);
|
||||
|
||||
const wrapper = mountPanel();
|
||||
await flushPromises();
|
||||
|
||||
const updated: WebhookResponse = { ...mockOrgWebhooks[0], url: 'https://updated.example.com/hook' };
|
||||
await wrapper.findComponent({ name: 'WebhookDialog' }).vm.$emit('saved', updated);
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.text()).toContain('https://updated.example.com/hook');
|
||||
});
|
||||
});
|
||||
});
|
||||
212
src/admin/tests/unit/views/AuditLogsView.spec.ts
Normal file
212
src/admin/tests/unit/views/AuditLogsView.spec.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { mount, flushPromises, type VueWrapper } from '@vue/test-utils';
|
||||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import AuditLogsView from '@/views/AuditLogsView.vue';
|
||||
import { useContextStore } from '@/stores/context';
|
||||
import type { ProjectResponse, AuditLogResponse } from '@/types/api';
|
||||
|
||||
jest.mock('@/api/auditLogs');
|
||||
jest.mock('@/api/client', () => ({
|
||||
setAuthTokens: jest.fn(),
|
||||
clearAuthTokens: jest.fn(),
|
||||
getAccessToken: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as auditLogsApi from '@/api/auditLogs';
|
||||
|
||||
const mockProject: ProjectResponse = {
|
||||
id: 10, name: 'Website', organizationId: 1, hideDisabledFlags: false, createdAt: '2026-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const mockLogs: AuditLogResponse[] = [
|
||||
{
|
||||
id: 1, resourceType: 'Feature', resourceId: '42', action: 'Created',
|
||||
changes: null, organizationId: 1, projectId: 10, environmentId: null,
|
||||
actorUserId: 5, createdAt: '2026-01-10T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 2, resourceType: 'FeatureState', resourceId: '99', action: 'Updated',
|
||||
changes: '{"enabled":true}', organizationId: 1, projectId: 10, environmentId: 100,
|
||||
actorUserId: 5, createdAt: '2026-01-11T11:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
function mountView() {
|
||||
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/', component: { template: '<div />' } }] });
|
||||
return mount(AuditLogsView, { global: { plugins: [router] } });
|
||||
}
|
||||
|
||||
describe('AuditLogsView', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('WhenNoProjectIsSelected', () => {
|
||||
it('ThenEmptyStateIsShown', () => {
|
||||
const wrapper = mountView();
|
||||
expect(wrapper.find('[data-testid="audit-logs-table"]').exists()).toBe(false);
|
||||
expect(wrapper.text()).toContain('Select a project');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenProjectIsSelected', () => {
|
||||
it('ThenAuditLogsAreLoaded', async () => {
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockResolvedValue({
|
||||
count: 2, next: null, previous: null, results: mockLogs,
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
mountView();
|
||||
await flushPromises();
|
||||
|
||||
expect(auditLogsApi.listAuditLogsByProject).toHaveBeenCalledWith(
|
||||
mockProject.id,
|
||||
expect.objectContaining({ page: 1 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('ThenAuditLogsTableIsRendered', async () => {
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockResolvedValue({
|
||||
count: 2, next: null, previous: null, results: mockLogs,
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="audit-logs-table"]').exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('Feature');
|
||||
expect(wrapper.text()).toContain('Created');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenResourceTypeFilterIsApplied', () => {
|
||||
it('ThenLogsAreReloadedWithFilter', async () => {
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockResolvedValue({
|
||||
count: 1, next: null, previous: null, results: [mockLogs[0]],
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockClear();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="filter-resource-type"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'Feature');
|
||||
await flushPromises();
|
||||
|
||||
expect(auditLogsApi.listAuditLogsByProject).toHaveBeenCalledWith(
|
||||
mockProject.id,
|
||||
expect.objectContaining({ resourceType: 'Feature', page: 1 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenActionFilterIsApplied', () => {
|
||||
it('ThenLogsAreReloadedWithActionFilter', async () => {
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockResolvedValue({
|
||||
count: 1, next: null, previous: null, results: [mockLogs[0]],
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockClear();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="filter-action"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'Created');
|
||||
await flushPromises();
|
||||
|
||||
expect(auditLogsApi.listAuditLogsByProject).toHaveBeenCalledWith(
|
||||
mockProject.id,
|
||||
expect.objectContaining({ action: 'Created', page: 1 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenDateRangeFilterIsApplied', () => {
|
||||
it('ThenLogsAreReloadedWithDateRange', async () => {
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockResolvedValue({
|
||||
count: 1, next: null, previous: null, results: [mockLogs[0]],
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockClear();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="filter-from"]') as VueWrapper<any>).vm.$emit('update:modelValue', '2026-01-01');
|
||||
await flushPromises();
|
||||
|
||||
expect(auditLogsApi.listAuditLogsByProject).toHaveBeenCalledWith(
|
||||
mockProject.id,
|
||||
expect.objectContaining({ from: '2026-01-01T00:00:00Z' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenClearFiltersIsClicked', () => {
|
||||
it('ThenFiltersAreResetAndLogsReloaded', async () => {
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockResolvedValue({
|
||||
count: 2, next: null, previous: null, results: mockLogs,
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockClear();
|
||||
|
||||
await wrapper.find('[data-testid="clear-filters-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(auditLogsApi.listAuditLogsByProject).toHaveBeenCalledWith(
|
||||
mockProject.id,
|
||||
expect.objectContaining({ resourceType: null, action: null, from: null, to: null }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenChangesExpandButtonIsClicked', () => {
|
||||
it('ThenChangesContentIsDisplayed', async () => {
|
||||
jest.mocked(auditLogsApi.listAuditLogsByProject).mockResolvedValue({
|
||||
count: 2, next: null, previous: null, results: mockLogs,
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization({ id: 1, name: 'Acme', createdAt: '' });
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
// log id=2 has changes
|
||||
await wrapper.find('[data-testid="expand-changes-2"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find('[data-testid="changes-2"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="changes-2"]').text()).toContain('enabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user