chore: add integration tests for import/save route regression coverage
This commit is contained in:
@@ -78,11 +78,20 @@ describe('import API', () => {
|
||||
mockedDelete.mockResolvedValue({});
|
||||
|
||||
await cancelImport(sessionUUID);
|
||||
|
||||
expect(client.delete).toHaveBeenCalledTimes(1);
|
||||
expect(client.delete).toHaveBeenCalledWith('/import/cancel', {
|
||||
params: {
|
||||
session_uuid: sessionUUID,
|
||||
},
|
||||
});
|
||||
|
||||
const [, requestConfig] = mockedDelete.mock.calls[0];
|
||||
expect(requestConfig).toEqual({
|
||||
params: {
|
||||
session_uuid: sessionUUID,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('forwards commitImport errors', async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { cancelJSONImport } from '../jsonImport';
|
||||
import { uploadJSONExport, commitJSONImport, cancelJSONImport } from '../jsonImport';
|
||||
import client from '../client';
|
||||
|
||||
vi.mock('../client', () => ({
|
||||
@@ -26,6 +26,67 @@ describe('jsonImport API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('uploadJSONExport posts upload endpoint with content payload', async () => {
|
||||
const content = '{"proxy_hosts":[]}';
|
||||
const mockResponse = {
|
||||
session: {
|
||||
id: 'json-session-456',
|
||||
state: 'reviewing',
|
||||
source: 'json',
|
||||
},
|
||||
preview: {
|
||||
hosts: [],
|
||||
conflicts: [],
|
||||
errors: [],
|
||||
},
|
||||
conflict_details: {},
|
||||
};
|
||||
|
||||
mockedPost.mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await uploadJSONExport(content);
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/import/json/upload', { content });
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('commitJSONImport posts commit endpoint with session_uuid, resolutions, and names body', async () => {
|
||||
const sessionUUID = 'json-session-789';
|
||||
const resolutions = { 'json.example.com': 'replace' };
|
||||
const names = { 'json.example.com': 'JSON Example' };
|
||||
const mockResponse = {
|
||||
created: 1,
|
||||
updated: 1,
|
||||
skipped: 0,
|
||||
errors: [],
|
||||
};
|
||||
|
||||
mockedPost.mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await commitJSONImport(sessionUUID, resolutions, names);
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/import/json/commit', {
|
||||
session_uuid: sessionUUID,
|
||||
resolutions,
|
||||
names,
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('forwards uploadJSONExport errors', async () => {
|
||||
const error = new Error('upload failed');
|
||||
mockedPost.mockRejectedValue(error);
|
||||
|
||||
await expect(uploadJSONExport('{"proxy_hosts":[]}')).rejects.toBe(error);
|
||||
});
|
||||
|
||||
it('forwards commitJSONImport errors', async () => {
|
||||
const error = new Error('commit failed');
|
||||
mockedPost.mockRejectedValue(error);
|
||||
|
||||
await expect(commitJSONImport('json-session-123', {}, {})).rejects.toBe(error);
|
||||
});
|
||||
|
||||
it('forwards cancelJSONImport errors', async () => {
|
||||
const error = new Error('cancel failed');
|
||||
mockedPost.mockRejectedValue(error);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { cancelNPMImport } from '../npmImport';
|
||||
import { uploadNPMExport, commitNPMImport, cancelNPMImport } from '../npmImport';
|
||||
import client from '../client';
|
||||
|
||||
vi.mock('../client', () => ({
|
||||
@@ -26,6 +26,67 @@ describe('npmImport API', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('uploadNPMExport posts upload endpoint with content payload', async () => {
|
||||
const content = '{"proxy_hosts":[]}';
|
||||
const mockResponse = {
|
||||
session: {
|
||||
id: 'npm-session-456',
|
||||
state: 'reviewing',
|
||||
source: 'npm',
|
||||
},
|
||||
preview: {
|
||||
hosts: [],
|
||||
conflicts: [],
|
||||
errors: [],
|
||||
},
|
||||
conflict_details: {},
|
||||
};
|
||||
|
||||
mockedPost.mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await uploadNPMExport(content);
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/import/npm/upload', { content });
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('commitNPMImport posts commit endpoint with session_uuid, resolutions, and names body', async () => {
|
||||
const sessionUUID = 'npm-session-789';
|
||||
const resolutions = { 'npm.example.com': 'replace' };
|
||||
const names = { 'npm.example.com': 'NPM Example' };
|
||||
const mockResponse = {
|
||||
created: 1,
|
||||
updated: 1,
|
||||
skipped: 0,
|
||||
errors: [],
|
||||
};
|
||||
|
||||
mockedPost.mockResolvedValue({ data: mockResponse });
|
||||
|
||||
const result = await commitNPMImport(sessionUUID, resolutions, names);
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/import/npm/commit', {
|
||||
session_uuid: sessionUUID,
|
||||
resolutions,
|
||||
names,
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('forwards uploadNPMExport errors', async () => {
|
||||
const error = new Error('upload failed');
|
||||
mockedPost.mockRejectedValue(error);
|
||||
|
||||
await expect(uploadNPMExport('{"proxy_hosts":[]}')).rejects.toBe(error);
|
||||
});
|
||||
|
||||
it('forwards commitNPMImport errors', async () => {
|
||||
const error = new Error('commit failed');
|
||||
mockedPost.mockRejectedValue(error);
|
||||
|
||||
await expect(commitNPMImport('npm-session-123', {}, {})).rejects.toBe(error);
|
||||
});
|
||||
|
||||
it('forwards cancelNPMImport errors', async () => {
|
||||
const error = new Error('cancel failed');
|
||||
mockedPost.mockRejectedValue(error);
|
||||
|
||||
@@ -29,6 +29,86 @@ describe('useJSONImport', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('sets preview and sessionId after successful upload', async () => {
|
||||
const uploadResponse = {
|
||||
session: {
|
||||
id: 'json-session-upload',
|
||||
state: 'reviewing',
|
||||
source: 'json',
|
||||
},
|
||||
preview: {
|
||||
hosts: [],
|
||||
conflicts: [],
|
||||
errors: [],
|
||||
},
|
||||
conflict_details: {},
|
||||
}
|
||||
|
||||
vi.mocked(api.uploadJSONExport).mockResolvedValue(uploadResponse)
|
||||
|
||||
const { result } = renderHook(() => useJSONImport(), { wrapper: createWrapper() })
|
||||
|
||||
await act(async () => {
|
||||
await result.current.upload('{"proxy_hosts":[]}')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessionId).toBe('json-session-upload')
|
||||
expect(result.current.preview).toEqual(uploadResponse)
|
||||
})
|
||||
})
|
||||
|
||||
it('commits active session and clears preview/session state', async () => {
|
||||
const uploadResponse = {
|
||||
session: {
|
||||
id: 'json-session-commit',
|
||||
state: 'reviewing',
|
||||
source: 'json',
|
||||
},
|
||||
preview: {
|
||||
hosts: [],
|
||||
conflicts: [],
|
||||
errors: [],
|
||||
},
|
||||
conflict_details: {},
|
||||
}
|
||||
const commitResponse = {
|
||||
created: 1,
|
||||
updated: 0,
|
||||
skipped: 0,
|
||||
errors: [],
|
||||
}
|
||||
|
||||
vi.mocked(api.uploadJSONExport).mockResolvedValue(uploadResponse)
|
||||
vi.mocked(api.commitJSONImport).mockResolvedValue(commitResponse)
|
||||
|
||||
const { result } = renderHook(() => useJSONImport(), { wrapper: createWrapper() })
|
||||
|
||||
await act(async () => {
|
||||
await result.current.upload('{"proxy_hosts":[]}')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessionId).toBe('json-session-commit')
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
await result.current.commit({ 'json.example.com': 'replace' }, { 'json.example.com': 'JSON Example' })
|
||||
})
|
||||
|
||||
expect(api.commitJSONImport).toHaveBeenCalledWith(
|
||||
'json-session-commit',
|
||||
{ 'json.example.com': 'replace' },
|
||||
{ 'json.example.com': 'JSON Example' }
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessionId).toBeNull()
|
||||
expect(result.current.preview).toBeNull()
|
||||
expect(result.current.commitResult).toEqual(commitResponse)
|
||||
})
|
||||
})
|
||||
|
||||
it('passes active session UUID to cancelJSONImport', async () => {
|
||||
const sessionId = 'json-session-123'
|
||||
vi.mocked(api.uploadJSONExport).mockResolvedValue({
|
||||
@@ -66,4 +146,45 @@ describe('useJSONImport', () => {
|
||||
expect(result.current.sessionId).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
it('returns No active session and skips cancel API call when session is missing', async () => {
|
||||
const { result } = renderHook(() => useJSONImport(), { wrapper: createWrapper() })
|
||||
|
||||
await expect(result.current.cancel()).rejects.toThrow('No active session')
|
||||
expect(api.cancelJSONImport).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('exposes commit error and preserves session on commit failure', async () => {
|
||||
const uploadResponse = {
|
||||
session: {
|
||||
id: 'json-session-error',
|
||||
state: 'reviewing',
|
||||
source: 'json',
|
||||
},
|
||||
preview: {
|
||||
hosts: [],
|
||||
conflicts: [],
|
||||
errors: [],
|
||||
},
|
||||
conflict_details: {},
|
||||
}
|
||||
const commitError = new Error('404 Not Found')
|
||||
|
||||
vi.mocked(api.uploadJSONExport).mockResolvedValue(uploadResponse)
|
||||
vi.mocked(api.commitJSONImport).mockRejectedValue(commitError)
|
||||
|
||||
const { result } = renderHook(() => useJSONImport(), { wrapper: createWrapper() })
|
||||
|
||||
await act(async () => {
|
||||
await result.current.upload('{"proxy_hosts":[]}')
|
||||
})
|
||||
|
||||
await expect(result.current.commit({}, {})).rejects.toBe(commitError)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.commitError).toBe(commitError)
|
||||
expect(result.current.sessionId).toBe('json-session-error')
|
||||
expect(result.current.preview).not.toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,6 +29,86 @@ describe('useNPMImport', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('sets preview and sessionId after successful upload', async () => {
|
||||
const uploadResponse = {
|
||||
session: {
|
||||
id: 'npm-session-upload',
|
||||
state: 'reviewing',
|
||||
source: 'npm',
|
||||
},
|
||||
preview: {
|
||||
hosts: [],
|
||||
conflicts: [],
|
||||
errors: [],
|
||||
},
|
||||
conflict_details: {},
|
||||
}
|
||||
|
||||
vi.mocked(api.uploadNPMExport).mockResolvedValue(uploadResponse)
|
||||
|
||||
const { result } = renderHook(() => useNPMImport(), { wrapper: createWrapper() })
|
||||
|
||||
await act(async () => {
|
||||
await result.current.upload('{"proxy_hosts":[]}')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessionId).toBe('npm-session-upload')
|
||||
expect(result.current.preview).toEqual(uploadResponse)
|
||||
})
|
||||
})
|
||||
|
||||
it('commits active session and clears preview/session state', async () => {
|
||||
const uploadResponse = {
|
||||
session: {
|
||||
id: 'npm-session-commit',
|
||||
state: 'reviewing',
|
||||
source: 'npm',
|
||||
},
|
||||
preview: {
|
||||
hosts: [],
|
||||
conflicts: [],
|
||||
errors: [],
|
||||
},
|
||||
conflict_details: {},
|
||||
}
|
||||
const commitResponse = {
|
||||
created: 1,
|
||||
updated: 0,
|
||||
skipped: 0,
|
||||
errors: [],
|
||||
}
|
||||
|
||||
vi.mocked(api.uploadNPMExport).mockResolvedValue(uploadResponse)
|
||||
vi.mocked(api.commitNPMImport).mockResolvedValue(commitResponse)
|
||||
|
||||
const { result } = renderHook(() => useNPMImport(), { wrapper: createWrapper() })
|
||||
|
||||
await act(async () => {
|
||||
await result.current.upload('{"proxy_hosts":[]}')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessionId).toBe('npm-session-commit')
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
await result.current.commit({ 'npm.example.com': 'replace' }, { 'npm.example.com': 'NPM Example' })
|
||||
})
|
||||
|
||||
expect(api.commitNPMImport).toHaveBeenCalledWith(
|
||||
'npm-session-commit',
|
||||
{ 'npm.example.com': 'replace' },
|
||||
{ 'npm.example.com': 'NPM Example' }
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessionId).toBeNull()
|
||||
expect(result.current.preview).toBeNull()
|
||||
expect(result.current.commitResult).toEqual(commitResponse)
|
||||
})
|
||||
})
|
||||
|
||||
it('passes active session UUID to cancelNPMImport', async () => {
|
||||
const sessionId = 'npm-session-123'
|
||||
vi.mocked(api.uploadNPMExport).mockResolvedValue({
|
||||
@@ -66,4 +146,45 @@ describe('useNPMImport', () => {
|
||||
expect(result.current.sessionId).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
it('returns No active session and skips cancel API call when session is missing', async () => {
|
||||
const { result } = renderHook(() => useNPMImport(), { wrapper: createWrapper() })
|
||||
|
||||
await expect(result.current.cancel()).rejects.toThrow('No active session')
|
||||
expect(api.cancelNPMImport).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('exposes commit error and preserves session on commit failure', async () => {
|
||||
const uploadResponse = {
|
||||
session: {
|
||||
id: 'npm-session-error',
|
||||
state: 'reviewing',
|
||||
source: 'npm',
|
||||
},
|
||||
preview: {
|
||||
hosts: [],
|
||||
conflicts: [],
|
||||
errors: [],
|
||||
},
|
||||
conflict_details: {},
|
||||
}
|
||||
const commitError = new Error('404 Not Found')
|
||||
|
||||
vi.mocked(api.uploadNPMExport).mockResolvedValue(uploadResponse)
|
||||
vi.mocked(api.commitNPMImport).mockRejectedValue(commitError)
|
||||
|
||||
const { result } = renderHook(() => useNPMImport(), { wrapper: createWrapper() })
|
||||
|
||||
await act(async () => {
|
||||
await result.current.upload('{"proxy_hosts":[]}')
|
||||
})
|
||||
|
||||
await expect(result.current.commit({}, {})).rejects.toBe(commitError)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.commitError).toBe(commitError)
|
||||
expect(result.current.sessionId).toBe('npm-session-error')
|
||||
expect(result.current.preview).not.toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
299
tests/integration/import-save-route-regression.spec.ts
Normal file
299
tests/integration/import-save-route-regression.spec.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
|
||||
import { getStorageStateAuthHeaders } from '../utils/api-helpers';
|
||||
|
||||
type SessionResponse = {
|
||||
session?: {
|
||||
id?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const SAMPLE_CADDYFILE = `example.com {
|
||||
reverse_proxy localhost:8080
|
||||
}`;
|
||||
|
||||
const SAMPLE_NPM_OR_JSON_EXPORT = JSON.stringify(
|
||||
{
|
||||
proxy_hosts: [
|
||||
{
|
||||
domain_names: ['route-regression.example.test'],
|
||||
forward_host: 'localhost',
|
||||
forward_port: 8080,
|
||||
forward_scheme: 'http',
|
||||
},
|
||||
],
|
||||
access_lists: [],
|
||||
certificates: [],
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
|
||||
function expectPredictableRouteMiss(status: number): void {
|
||||
expect([404, 405]).toContain(status);
|
||||
}
|
||||
|
||||
function expectCanonicalNon404(status: number): void {
|
||||
expect(status).not.toBe(404);
|
||||
expect(status).toBeLessThan(500);
|
||||
}
|
||||
|
||||
async function readSessionId(response: import('@playwright/test').APIResponse): Promise<string> {
|
||||
const data = (await response.json()) as SessionResponse;
|
||||
const sessionId = data?.session?.id;
|
||||
expect(sessionId).toBeTruthy();
|
||||
return sessionId as string;
|
||||
}
|
||||
|
||||
test.describe('Import/Save Route Regression Coverage', () => {
|
||||
test('Caddy import flow stages use canonical routes and reject route drift', async ({ page, adminUser }) => {
|
||||
await loginUser(page, adminUser);
|
||||
const headers = getStorageStateAuthHeaders();
|
||||
|
||||
await test.step('Open Caddy import page and validate route-negative probes', async () => {
|
||||
await page.goto('/tasks/import/caddyfile', { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.getByRole('heading', { level: 1 })).toContainText(/import/i);
|
||||
await expect(page.getByRole('button', { name: /parse|review/i })).toBeVisible();
|
||||
|
||||
const wrongStatusMethod = await page.request.post('/api/v1/import/status', {
|
||||
headers,
|
||||
data: {},
|
||||
});
|
||||
expectPredictableRouteMiss(wrongStatusMethod.status());
|
||||
|
||||
const wrongUploadMethod = await page.request.get('/api/v1/import/upload', { headers });
|
||||
expectPredictableRouteMiss(wrongUploadMethod.status());
|
||||
|
||||
const wrongCancelMethod = await page.request.post('/api/v1/import/cancel', {
|
||||
headers,
|
||||
data: {},
|
||||
});
|
||||
expectPredictableRouteMiss(wrongCancelMethod.status());
|
||||
});
|
||||
|
||||
await test.step('Run canonical Caddy import status/upload/cancel path', async () => {
|
||||
const statusResponse = await page.request.get('/api/v1/import/status', { headers });
|
||||
expectCanonicalNon404(statusResponse.status());
|
||||
|
||||
const uploadForCancel = await page.request.post('/api/v1/import/upload', {
|
||||
headers,
|
||||
data: { content: SAMPLE_CADDYFILE },
|
||||
});
|
||||
expectCanonicalNon404(uploadForCancel.status());
|
||||
const cancelSessionId = await readSessionId(uploadForCancel);
|
||||
|
||||
const cancelResponse = await page.request.delete('/api/v1/import/cancel', {
|
||||
headers,
|
||||
params: { session_uuid: cancelSessionId },
|
||||
});
|
||||
expectCanonicalNon404(cancelResponse.status());
|
||||
});
|
||||
|
||||
await test.step('Run canonical Caddy preview/backup-before-commit/commit/post-state path', async () => {
|
||||
const uploadForCommit = await page.request.post('/api/v1/import/upload', {
|
||||
headers,
|
||||
data: { content: SAMPLE_CADDYFILE },
|
||||
});
|
||||
expectCanonicalNon404(uploadForCommit.status());
|
||||
const commitSessionId = await readSessionId(uploadForCommit);
|
||||
|
||||
const previewResponse = await page.request.get('/api/v1/import/preview', { headers });
|
||||
expectCanonicalNon404(previewResponse.status());
|
||||
|
||||
const backupBeforeCommit = await page.request.post('/api/v1/backups', {
|
||||
headers,
|
||||
data: {},
|
||||
});
|
||||
expectCanonicalNon404(backupBeforeCommit.status());
|
||||
|
||||
const commitResponse = await page.request.post('/api/v1/import/commit', {
|
||||
headers,
|
||||
data: {
|
||||
session_uuid: commitSessionId,
|
||||
resolutions: {},
|
||||
names: {},
|
||||
},
|
||||
});
|
||||
expectCanonicalNon404(commitResponse.status());
|
||||
|
||||
const postState = await page.request.get('/api/v1/import/status', { headers });
|
||||
expectCanonicalNon404(postState.status());
|
||||
});
|
||||
});
|
||||
|
||||
test('NPM and JSON import critical routes pass canonical methods and reject drift', async ({ page, adminUser }) => {
|
||||
await loginUser(page, adminUser);
|
||||
const headers = getStorageStateAuthHeaders();
|
||||
|
||||
await test.step('NPM import upload/commit/cancel with route-mismatch checks', async () => {
|
||||
await page.goto('/tasks/import/npm', { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.getByRole('heading').filter({ hasText: /npm/i }).first()).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /upload\s*&\s*preview/i })).toBeVisible();
|
||||
|
||||
const npmWrongMethod = await page.request.get('/api/v1/import/npm/upload', { headers });
|
||||
expectPredictableRouteMiss(npmWrongMethod.status());
|
||||
|
||||
const npmCancelWrongPath = await page.request.post('/api/v1/import/npm/cancel-session', {
|
||||
headers,
|
||||
data: {},
|
||||
});
|
||||
expectPredictableRouteMiss(npmCancelWrongPath.status());
|
||||
|
||||
const npmUploadForCancel = await page.request.post('/api/v1/import/npm/upload', {
|
||||
headers,
|
||||
data: { content: SAMPLE_NPM_OR_JSON_EXPORT },
|
||||
});
|
||||
expectCanonicalNon404(npmUploadForCancel.status());
|
||||
const npmCancelSession = await readSessionId(npmUploadForCancel);
|
||||
|
||||
const npmCancel = await page.request.post('/api/v1/import/npm/cancel', {
|
||||
headers,
|
||||
data: { session_uuid: npmCancelSession },
|
||||
});
|
||||
expectCanonicalNon404(npmCancel.status());
|
||||
|
||||
const npmUploadForCommit = await page.request.post('/api/v1/import/npm/upload', {
|
||||
headers,
|
||||
data: { content: SAMPLE_NPM_OR_JSON_EXPORT },
|
||||
});
|
||||
expectCanonicalNon404(npmUploadForCommit.status());
|
||||
const npmCommitSession = await readSessionId(npmUploadForCommit);
|
||||
|
||||
const npmCommit = await page.request.post('/api/v1/import/npm/commit', {
|
||||
headers,
|
||||
data: {
|
||||
session_uuid: npmCommitSession,
|
||||
resolutions: {},
|
||||
names: {},
|
||||
},
|
||||
});
|
||||
expectCanonicalNon404(npmCommit.status());
|
||||
});
|
||||
|
||||
await test.step('JSON import upload/commit/cancel with route-mismatch checks', async () => {
|
||||
await page.goto('/tasks/import/json', { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.getByRole('heading').filter({ hasText: /json/i }).first()).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /upload\s*&\s*preview/i })).toBeVisible();
|
||||
|
||||
const jsonWrongMethod = await page.request.get('/api/v1/import/json/upload', { headers });
|
||||
expectPredictableRouteMiss(jsonWrongMethod.status());
|
||||
|
||||
const jsonCommitWrongPath = await page.request.post('/api/v1/import/json/commit-now', {
|
||||
headers,
|
||||
data: {},
|
||||
});
|
||||
expectPredictableRouteMiss(jsonCommitWrongPath.status());
|
||||
|
||||
const jsonUploadForCancel = await page.request.post('/api/v1/import/json/upload', {
|
||||
headers,
|
||||
data: { content: SAMPLE_NPM_OR_JSON_EXPORT },
|
||||
});
|
||||
expectCanonicalNon404(jsonUploadForCancel.status());
|
||||
const jsonCancelSession = await readSessionId(jsonUploadForCancel);
|
||||
|
||||
const jsonCancel = await page.request.post('/api/v1/import/json/cancel', {
|
||||
headers,
|
||||
data: { session_uuid: jsonCancelSession },
|
||||
});
|
||||
expectCanonicalNon404(jsonCancel.status());
|
||||
|
||||
const jsonUploadForCommit = await page.request.post('/api/v1/import/json/upload', {
|
||||
headers,
|
||||
data: { content: SAMPLE_NPM_OR_JSON_EXPORT },
|
||||
});
|
||||
expectCanonicalNon404(jsonUploadForCommit.status());
|
||||
const jsonCommitSession = await readSessionId(jsonUploadForCommit);
|
||||
|
||||
const jsonCommit = await page.request.post('/api/v1/import/json/commit', {
|
||||
headers,
|
||||
data: {
|
||||
session_uuid: jsonCommitSession,
|
||||
resolutions: {},
|
||||
names: {},
|
||||
},
|
||||
});
|
||||
expectCanonicalNon404(jsonCommit.status());
|
||||
});
|
||||
});
|
||||
|
||||
test('Save flow routes for settings and proxy-host paths detect 404 regressions', async ({ page, adminUser }) => {
|
||||
await loginUser(page, adminUser);
|
||||
const headers = getStorageStateAuthHeaders();
|
||||
let createdProxyUUID = '';
|
||||
|
||||
await test.step('System settings save path succeeds on canonical route', async () => {
|
||||
await page.goto('/settings/system', { waitUntil: 'domcontentloaded' });
|
||||
await expect(page.getByRole('heading', { name: /system settings/i })).toBeVisible();
|
||||
|
||||
const saveButton = page.getByRole('button', { name: /save settings/i }).first();
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
const saveResponsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/api/v1/settings') &&
|
||||
['POST', 'PATCH'].includes(response.request().method())
|
||||
);
|
||||
|
||||
await saveButton.click();
|
||||
const saveResponse = await saveResponsePromise;
|
||||
expectCanonicalNon404(saveResponse.status());
|
||||
|
||||
const wrongSettingsMethod = await page.request.delete('/api/v1/settings', { headers });
|
||||
expectPredictableRouteMiss(wrongSettingsMethod.status());
|
||||
});
|
||||
|
||||
await test.step('Proxy-host save path succeeds on canonical route and rejects wrong method/path', async () => {
|
||||
const unique = `${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
||||
const createResponse = await page.request.post('/api/v1/proxy-hosts', {
|
||||
headers,
|
||||
data: {
|
||||
name: `PR3 Route Regression ${unique}`,
|
||||
domain_names: `pr3-route-${unique}.example.test`,
|
||||
forward_host: 'localhost',
|
||||
forward_port: 8080,
|
||||
forward_scheme: 'http',
|
||||
websocket_support: false,
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
expectCanonicalNon404(createResponse.status());
|
||||
expect([200, 201]).toContain(createResponse.status());
|
||||
|
||||
const created = (await createResponse.json()) as { uuid?: string };
|
||||
createdProxyUUID = created.uuid || '';
|
||||
expect(createdProxyUUID).toBeTruthy();
|
||||
|
||||
const updateResponse = await page.request.put(`/api/v1/proxy-hosts/${createdProxyUUID}`, {
|
||||
headers,
|
||||
data: {
|
||||
name: `PR3 Route Regression Updated ${unique}`,
|
||||
domain_names: `pr3-route-${unique}.example.test`,
|
||||
forward_host: 'localhost',
|
||||
forward_port: 8081,
|
||||
forward_scheme: 'http',
|
||||
websocket_support: false,
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
expectCanonicalNon404(updateResponse.status());
|
||||
|
||||
const wrongProxyMethod = await page.request.post(`/api/v1/proxy-hosts/${createdProxyUUID}`, {
|
||||
headers,
|
||||
data: {},
|
||||
});
|
||||
expectPredictableRouteMiss(wrongProxyMethod.status());
|
||||
|
||||
const wrongProxyPath = await page.request.put('/api/v1/proxy-host', {
|
||||
headers,
|
||||
data: {},
|
||||
});
|
||||
expectPredictableRouteMiss(wrongProxyPath.status());
|
||||
});
|
||||
|
||||
if (createdProxyUUID) {
|
||||
await test.step('Cleanup created proxy host', async () => {
|
||||
const cleanup = await page.request.delete(`/api/v1/proxy-hosts/${createdProxyUUID}`, { headers });
|
||||
expectCanonicalNon404(cleanup.status());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user