mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-25 14:25:06 +00:00
- Add query-builder feature to features.json configuration - Create /api/admin/query-builder endpoint with full validation - Build QueryBuilderTab component with visual query construction - Add WHERE conditions builder with multiple operators - Add ORDER BY, LIMIT, and OFFSET support - Add query operators configuration - Create comprehensive integration tests - Add getQueryOperators utility function - Update navigation to include Query Builder Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
334 lines
8.8 KiB
TypeScript
334 lines
8.8 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
test.describe('Query Builder API', () => {
|
|
test.describe('Authentication', () => {
|
|
test('should reject query builder without authentication', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401);
|
|
const data = await response.json();
|
|
expect(data.error).toBe('Unauthorized');
|
|
});
|
|
});
|
|
|
|
test.describe('Input Validation', () => {
|
|
test('should reject query without table name', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject query with invalid table name', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users; DROP TABLE users--',
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth, but would be 400 if authenticated
|
|
});
|
|
|
|
test('should reject query with invalid column name', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
columns: ['id', 'name; DROP TABLE--'],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject query with invalid operator', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
where: [
|
|
{
|
|
column: 'id',
|
|
operator: 'EXEC',
|
|
value: '1',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject IN operator without array value', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
where: [
|
|
{
|
|
column: 'id',
|
|
operator: 'IN',
|
|
value: 'not-an-array',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject operator requiring value without value', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
where: [
|
|
{
|
|
column: 'id',
|
|
operator: '=',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject invalid LIMIT value', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
limit: -5,
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject invalid OFFSET value', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
offset: 'invalid',
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
});
|
|
|
|
test.describe('Query Building', () => {
|
|
test('should accept valid table name', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'test_table',
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept query with column selection', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
columns: ['id', 'name', 'email'],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept query with WHERE conditions', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
where: [
|
|
{
|
|
column: 'id',
|
|
operator: '=',
|
|
value: 1,
|
|
},
|
|
{
|
|
column: 'name',
|
|
operator: 'LIKE',
|
|
value: '%john%',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept IS NULL operator without value', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
where: [
|
|
{
|
|
column: 'email',
|
|
operator: 'IS NULL',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept IS NOT NULL operator without value', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
where: [
|
|
{
|
|
column: 'email',
|
|
operator: 'IS NOT NULL',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept IN operator with array value', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
where: [
|
|
{
|
|
column: 'id',
|
|
operator: 'IN',
|
|
value: [1, 2, 3],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept query with ORDER BY', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
orderBy: {
|
|
column: 'created_at',
|
|
direction: 'DESC',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept query with LIMIT', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
limit: 10,
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept query with OFFSET', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
offset: 5,
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should accept comprehensive query', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
columns: ['id', 'name', 'email'],
|
|
where: [
|
|
{
|
|
column: 'id',
|
|
operator: '>',
|
|
value: 5,
|
|
},
|
|
{
|
|
column: 'name',
|
|
operator: 'LIKE',
|
|
value: '%admin%',
|
|
},
|
|
],
|
|
orderBy: {
|
|
column: 'id',
|
|
direction: 'ASC',
|
|
},
|
|
limit: 20,
|
|
offset: 10,
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
});
|
|
|
|
test.describe('SQL Injection Prevention', () => {
|
|
test('should reject SQL injection in table name', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: "users' OR '1'='1",
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject SQL injection in column name', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
columns: ["id'; DROP TABLE users--"],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject SQL injection in WHERE column', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
where: [
|
|
{
|
|
column: "id'; DELETE FROM users--",
|
|
operator: '=',
|
|
value: 1,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
|
|
test('should reject SQL injection in ORDER BY column', async ({ page }) => {
|
|
const response = await page.request.post('/api/admin/query-builder', {
|
|
data: {
|
|
table: 'users',
|
|
orderBy: {
|
|
column: "id'; DROP TABLE--",
|
|
direction: 'ASC',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(response.status()).toBe(401); // No auth
|
|
});
|
|
});
|
|
});
|