Files
nimbus/resources/js/tests/stores/useRequestBuilderStore.test.ts
Mazen Touati e1b844cee0 feat(history): add history viewer and rewind (#38)
* feat(ui): add `input group` base component

* feat(history): add history viewer and rewind

* test: update selector snapshot

* test: add PW base page

* style: apply TS style fixes

* chore(history): request history wiki

* chore(history): remove unwanted symbol

* chore: fix type

* style: apply TS style fixes
2026-01-17 20:50:00 +01:00

297 lines
9.4 KiB
TypeScript

import { AuthorizationContract, ParameterContract, ParameterType } from '@/interfaces';
import { AuthorizationType } from '@/interfaces/generated';
import { PendingRequest, RequestBodyTypeEnum } from '@/interfaces/http';
import { RouteDefinition } from '@/interfaces/routes';
import { useRequestBuilderStore } from '@/stores/request/useRequestBuilderStore';
import { createPinia, setActivePinia } from 'pinia';
import { describe, expect, it, vi } from 'vitest';
import { reactive } from 'vue';
const preferences = reactive({
autoRefreshRoutes: true,
maxHistoryLogs: 100,
theme: 'system' as const,
defaultRequestBodyType: -1 as RequestBodyTypeEnum | -1,
defaultAuthorizationType: AuthorizationType.CurrentUser,
});
const apiUrl = 'https://api.example.com';
vi.mock('@/stores', async importOriginal => {
const actual = await importOriginal<object>();
return {
...actual,
useSettingsStore: () => ({
preferences,
}),
useConfigStore: () => ({
apiUrl,
}),
};
});
const baseRoute: RouteDefinition = {
method: 'GET',
endpoint: 'users',
shortEndpoint: 'users',
schema: {
shape: {
properties: {
name: {
type: 'string',
},
},
},
extractionErrors: null,
},
};
describe('useRequestBuilderStore', () => {
const createStore = () => {
setActivePinia(createPinia());
return useRequestBuilderStore();
};
it('initializes pending request data with defaults', () => {
const store = createStore();
store.initializeRequest(baseRoute, [baseRoute]);
const pending = store.pendingRequestData as PendingRequest;
expect(pending.method).toBe('GET');
expect(pending.endpoint).toBe('users');
expect(pending.payloadType).toBe(RequestBodyTypeEnum.JSON);
expect(pending.schema).toMatchObject(baseRoute.schema);
expect(pending.authorization).toEqual({
type: AuthorizationType.CurrentUser,
});
});
it('switches schema and payload when method changes', () => {
const store = createStore();
const postRoute: RouteDefinition = {
...baseRoute,
method: 'POST',
schema: {
shape: {},
extractionErrors: null,
},
};
store.initializeRequest(baseRoute, [baseRoute, postRoute]);
store.updateRequestMethod('POST');
const pending = store.pendingRequestData as PendingRequest;
expect(pending.method).toBe('POST');
expect(pending.payloadType).toBe(RequestBodyTypeEnum.EMPTY);
expect(pending.schema).toMatchObject(postRoute.schema);
});
it('falls back to empty payload when route definition missing', () => {
const store = createStore();
store.initializeRequest(baseRoute, [baseRoute]);
store.updateRequestMethod('DELETE');
const pending = store.pendingRequestData as PendingRequest;
expect(pending.payloadType).toBe(RequestBodyTypeEnum.EMPTY);
});
it('updates headers, body, query parameters, and authorization', () => {
const store = createStore();
store.initializeRequest(baseRoute, [baseRoute]);
const headers: ParameterContract[] = [
{
type: ParameterType.Text,
key: 'Content-Type',
value: 'application/json',
enabled: true,
},
];
const body: PendingRequest['body'] = {
GET: { [RequestBodyTypeEnum.JSON]: '{}' },
};
const params: ParameterContract[] = [
{ type: ParameterType.Text, key: 'page', value: '1', enabled: true },
];
const auth: AuthorizationContract = {
type: AuthorizationType.Bearer,
value: 'token',
};
store.updateRequestHeaders(headers);
store.updateRequestBody(body);
store.updateQueryParameters(params);
store.updateAuthorization(auth);
const pending = store.pendingRequestData as PendingRequest;
expect(pending.headers).toEqual(headers);
expect(pending.body).toEqual(body);
expect(pending.queryParameters).toEqual(params);
expect(pending.authorization).toEqual(auth);
});
it('builds request url using config base path', () => {
const store = createStore();
store.initializeRequest(baseRoute, [baseRoute]);
const pending = store.pendingRequestData as PendingRequest;
pending.queryParameters = [
{ type: ParameterType.Text, key: 'page', value: '1', enabled: true },
];
expect(store.getRequestUrl(pending)).toBe('https://api.example.com/users?page=1');
});
it('resets pending request state', () => {
const store = createStore();
store.initializeRequest(baseRoute, [baseRoute]);
store.resetRequest();
expect(store.pendingRequestData).toBeNull();
});
it('restores state from a historical request', () => {
const store = createStore();
store.initializeRequest(baseRoute, [baseRoute]);
const historicalRequest = {
method: 'POST',
endpoint: 'users',
headers: [
{
type: ParameterType.Text,
key: 'X-RequestHistory',
value: 'true',
enabled: true,
},
],
body: '{"restored": true}',
queryParameters: [
{
type: ParameterType.Text,
key: 'restored',
value: 'true',
enabled: true,
},
],
payloadType: RequestBodyTypeEnum.JSON,
authorization: { type: AuthorizationType.None },
routeDefinition: baseRoute,
};
// @ts-expect-error simplified for test
store.restoreFromHistory(historicalRequest);
const pending = store.pendingRequestData as PendingRequest;
expect(pending.method).toBe('POST');
expect(pending.endpoint).toBe('users');
expect(pending.headers).toEqual(historicalRequest.headers);
expect(pending.queryParameters).toEqual(historicalRequest.queryParameters);
expect(pending.payloadType).toBe(RequestBodyTypeEnum.JSON);
expect(pending.body.POST?.[RequestBodyTypeEnum.JSON]).toBe(
historicalRequest.body,
);
});
it('preserves existing headers when initializing a new request', () => {
const store = createStore();
// Initial request
store.initializeRequest(baseRoute, [baseRoute]);
const initialHeaders: ParameterContract[] = [
{ type: ParameterType.Text, key: 'X-Test', value: 'test', enabled: true },
];
store.updateRequestHeaders(initialHeaders);
// Initialize new request (endpoint switch)
const nextRoute: RouteDefinition = { ...baseRoute, endpoint: 'posts' };
store.initializeRequest(nextRoute, [nextRoute]);
const pending = store.pendingRequestData as PendingRequest;
expect(pending.endpoint).toBe('posts');
expect(pending.headers).toEqual(initialHeaders);
});
it('deep clones parameters when restoring from history to prevent shared references', () => {
const store = createStore();
store.initializeRequest(baseRoute, [baseRoute]);
const historicalRequest = {
method: 'GET',
endpoint: 'users',
headers: [
{
id: 1,
type: ParameterType.Text,
key: 'X-Custom',
value: 'header1',
enabled: true,
},
{
id: 2,
type: ParameterType.Text,
key: 'X-Disabled',
value: 'header2',
enabled: false,
},
],
body: null,
queryParameters: [
{
id: 3,
type: ParameterType.Text,
key: 'active',
value: 'yes',
enabled: true,
},
{
id: 4,
type: ParameterType.Text,
key: 'inactive',
value: 'no',
enabled: false,
},
],
payloadType: RequestBodyTypeEnum.EMPTY,
authorization: { type: AuthorizationType.None },
routeDefinition: baseRoute,
};
// @ts-expect-error simplified for test
store.restoreFromHistory(historicalRequest);
const pending = store.pendingRequestData as PendingRequest;
// Verify values are restored correctly
expect(pending.headers).toEqual(historicalRequest.headers);
expect(pending.queryParameters).toEqual(historicalRequest.queryParameters);
// Modify the restored parameters (simulating UI interaction)
pending.headers[1].enabled = true;
pending.queryParameters[1].enabled = true;
// Verify the original historical request objects are NOT modified (no shared references)
expect(historicalRequest.headers[1].enabled).toBe(false);
expect(historicalRequest.queryParameters[1].enabled).toBe(false);
});
});