Negative Test Generator — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Negative Test Generator (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert QA engineer specializing in negative testing and robustness verification. When the user asks you to create, review, or improve negative test cases, follow these detailed instructions to systematically generate tests that verify the system correctly rejects invalid inputs, handles error conditions gracefully, and maintains data integrity under adversarial conditions.
tests/
negative/
generators/
input-type-violations.ts
missing-fields.ts
boundary-violations.ts
format-violations.ts
injection-payloads.ts
concurrent-conflicts.ts
helpers/
error-assertions.ts
payload-builder.ts
type-fuzzer.ts
tests/
api/
create-user.negative.test.ts
update-order.negative.test.ts
authentication.negative.test.ts
authorization.negative.test.ts
validation/
email-validation.negative.test.ts
number-validation.negative.test.ts
date-validation.negative.test.ts
string-validation.negative.test.ts
payloads/
malformed-json.negative.test.ts
oversized-payload.negative.test.ts
content-type-mismatch.negative.test.ts
concurrency/
race-condition.negative.test.ts
duplicate-submission.negative.test.ts
config/
negative-test.config.ts
data/
injection-strings.json
boundary-values.jsonThe foundation of negative testing is systematically generating invalid type variants for every field.
// input-type-violations.ts
type FieldType = 'string' | 'number' | 'boolean' | 'email' | 'url' | 'date' | 'uuid' | 'enum' | 'array' | 'object';
interface FieldDefinition {
name: string;
type: FieldType;
required: boolean;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
pattern?: string;
enumValues?: string[];
format?: string;
}
interface NegativeTestCase {
name: string;
field: string;
category: NegativeCategory;
input: any;
expectedError: {
status?: number;
code?: string;
messagePattern?: RegExp;
field?: string;
};
}
type NegativeCategory =
| 'type_violation'
| 'missing_required'
| 'boundary_violation'
| 'format_violation'
| 'injection'
| 'null_undefined'
| 'empty_value'
| 'overflow'
| 'encoding'
| 'special_characters';
function generateTypeViolations(field: FieldDefinition): NegativeTestCase[] {
const cases: NegativeTestCase[] = [];
const typeInvalids: Record<FieldType, any[]> = {
string: [123, true, [], {}, 0, -1, NaN, Infinity],
number: ['abc', '', true, [], {}, 'NaN', '123abc', null],
boolean: ['yes', 'no', 1, 0, 'true', 'false', '', null],
email: [123, true, [], {}, null],
url: [123, true, [], {}, null],
date: [123, true, [], {}, 'not-a-date', null],
uuid: [123, true, [], {}, 'not-a-uuid', null],
enum: [123, true, [], {}, null],
array: ['string', 123, true, {}, null],
object: ['string', 123, true, [], null],
};
const invalids = typeInvalids[field.type] || [];
for (const invalidValue of invalids) {
cases.push({
name: `${field.name}: should reject ${typeof invalidValue} (${JSON.stringify(invalidValue)}) when ${field.type} expected`,
field: field.name,
category: 'type_violation',
input: invalidValue,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: new RegExp(`${field.name}.*(?:invalid|expected|must be)`, 'i'),
field: field.name,
},
});
}
return cases;
}
function generateMissingFieldCases(fields: FieldDefinition[]): NegativeTestCase[] {
const cases: NegativeTestCase[] = [];
for (const field of fields.filter(f => f.required)) {
// Missing entirely
cases.push({
name: `${field.name}: should reject when required field is missing entirely`,
field: field.name,
category: 'missing_required',
input: undefined,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: new RegExp(`${field.name}.*required`, 'i'),
field: field.name,
},
});
// Explicitly null
cases.push({
name: `${field.name}: should reject null for required field`,
field: field.name,
category: 'null_undefined',
input: null,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
field: field.name,
},
});
// Explicitly undefined
cases.push({
name: `${field.name}: should reject undefined for required field`,
field: field.name,
category: 'null_undefined',
input: undefined,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
field: field.name,
},
});
// Empty string (for string types)
if (field.type === 'string' || field.type === 'email' || field.type === 'url') {
cases.push({
name: `${field.name}: should reject empty string for required field`,
field: field.name,
category: 'empty_value',
input: '',
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
field: field.name,
},
});
cases.push({
name: `${field.name}: should reject whitespace-only string for required field`,
field: field.name,
category: 'empty_value',
input: ' ',
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
field: field.name,
},
});
}
// Empty array (for array types)
if (field.type === 'array') {
cases.push({
name: `${field.name}: should reject empty array for required field`,
field: field.name,
category: 'empty_value',
input: [],
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
field: field.name,
},
});
}
}
return cases;
}// boundary-violations.ts
function generateBoundaryViolations(field: FieldDefinition): NegativeTestCase[] {
const cases: NegativeTestCase[] = [];
// String length boundaries
if (field.type === 'string' || field.type === 'email' || field.type === 'url') {
if (field.minLength !== undefined) {
cases.push({
name: `${field.name}: should reject string shorter than minLength (${field.minLength})`,
field: field.name,
category: 'boundary_violation',
input: 'a'.repeat(Math.max(0, field.minLength - 1)),
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: new RegExp(`${field.name}.*(?:min|short|least|minimum)`, 'i'),
},
});
}
if (field.maxLength !== undefined) {
cases.push({
name: `${field.name}: should reject string exceeding maxLength (${field.maxLength})`,
field: field.name,
category: 'boundary_violation',
input: 'a'.repeat(field.maxLength + 1),
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: new RegExp(`${field.name}.*(?:max|long|exceed|maximum)`, 'i'),
},
});
// Significantly exceeding max length
cases.push({
name: `${field.name}: should reject extremely long string (10x maxLength)`,
field: field.name,
category: 'overflow',
input: 'a'.repeat(field.maxLength * 10),
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
},
});
}
}
// Numeric boundaries
if (field.type === 'number') {
if (field.min !== undefined) {
cases.push({
name: `${field.name}: should reject number below minimum (${field.min})`,
field: field.name,
category: 'boundary_violation',
input: field.min - 1,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: new RegExp(`${field.name}.*(?:min|least|greater)`, 'i'),
},
});
cases.push({
name: `${field.name}: should reject large negative number`,
field: field.name,
category: 'boundary_violation',
input: -Number.MAX_SAFE_INTEGER,
expectedError: { status: 400, code: 'VALIDATION_ERROR' },
});
}
if (field.max !== undefined) {
cases.push({
name: `${field.name}: should reject number above maximum (${field.max})`,
field: field.name,
category: 'boundary_violation',
input: field.max + 1,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: new RegExp(`${field.name}.*(?:max|exceed|less)`, 'i'),
},
});
}
// Special numeric values
cases.push({
name: `${field.name}: should reject NaN`,
field: field.name,
category: 'type_violation',
input: NaN,
expectedError: { status: 400, code: 'VALIDATION_ERROR' },
});
cases.push({
name: `${field.name}: should reject Infinity`,
field: field.name,
category: 'type_violation',
input: Infinity,
expectedError: { status: 400, code: 'VALIDATION_ERROR' },
});
cases.push({
name: `${field.name}: should reject -Infinity`,
field: field.name,
category: 'type_violation',
input: -Infinity,
expectedError: { status: 400, code: 'VALIDATION_ERROR' },
});
cases.push({
name: `${field.name}: should handle -0 correctly`,
field: field.name,
category: 'type_violation',
input: -0,
expectedError: { status: 400, code: 'VALIDATION_ERROR' },
});
}
return cases;
}// format-violations.ts
function generateFormatViolations(field: FieldDefinition): NegativeTestCase[] {
const cases: NegativeTestCase[] = [];
if (field.type === 'email') {
const invalidEmails = [
{ value: 'notanemail', reason: 'missing @ symbol' },
{ value: '@domain.com', reason: 'missing local part' },
{ value: 'user@', reason: 'missing domain' },
{ value: '[email protected]', reason: 'domain starts with dot' },
{ value: 'user@domain', reason: 'missing TLD' },
{ value: 'user @domain.com', reason: 'contains space' },
{ value: 'user@dom ain.com', reason: 'space in domain' },
{ value: 'user@@domain.com', reason: 'double @ symbol' },
{ value: '[email protected]', reason: 'consecutive dots in domain' },
{ value: '<script>@domain.com', reason: 'HTML in local part' },
{ value: '[email protected]\n', reason: 'trailing newline' },
{ value: '', reason: 'empty string' },
];
for (const { value, reason } of invalidEmails) {
cases.push({
name: `${field.name}: should reject invalid email -- ${reason}`,
field: field.name,
category: 'format_violation',
input: value,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: /email.*invalid|invalid.*email/i,
},
});
}
}
if (field.type === 'url') {
const invalidUrls = [
{ value: 'not-a-url', reason: 'no protocol' },
{ value: 'htp://example.com', reason: 'invalid protocol' },
{ value: 'http://', reason: 'missing host' },
{ value: 'http://example', reason: 'no TLD' },
{ value: '://example.com', reason: 'missing protocol name' },
{ value: 'http://exam ple.com', reason: 'space in host' },
{ value: 'javascript:alert(1)', reason: 'javascript protocol' },
{ value: 'data:text/html,<script>alert(1)</script>', reason: 'data URI with script' },
];
for (const { value, reason } of invalidUrls) {
cases.push({
name: `${field.name}: should reject invalid URL -- ${reason}`,
field: field.name,
category: 'format_violation',
input: value,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: /url.*invalid|invalid.*url/i,
},
});
}
}
if (field.type === 'date') {
const invalidDates = [
{ value: '2024-13-01', reason: 'month 13' },
{ value: '2024-02-30', reason: 'February 30th' },
{ value: '2024-00-01', reason: 'month 0' },
{ value: '2024-01-32', reason: 'day 32' },
{ value: '2024-01-00', reason: 'day 0' },
{ value: '24-01-15', reason: 'two-digit year' },
{ value: 'January 15, 2024', reason: 'non-ISO format' },
{ value: '01/15/2024', reason: 'US format slashes' },
{ value: '2024/01/15', reason: 'forward slashes' },
{ value: 'not-a-date', reason: 'non-date string' },
{ value: '9999-12-31T23:59:59.999Z', reason: 'far future date' },
{ value: '0000-01-01', reason: 'year zero' },
];
for (const { value, reason } of invalidDates) {
cases.push({
name: `${field.name}: should reject invalid date -- ${reason}`,
field: field.name,
category: 'format_violation',
input: value,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
},
});
}
}
if (field.type === 'uuid') {
const invalidUuids = [
{ value: 'not-a-uuid', reason: 'non-UUID string' },
{ value: '12345678-1234-1234-1234-12345678901', reason: 'too short' },
{ value: '12345678-1234-1234-1234-1234567890123', reason: 'too long' },
{ value: '12345678123412341234123456789012', reason: 'missing hyphens' },
{ value: 'ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ', reason: 'non-hex characters' },
{ value: '00000000-0000-0000-0000-000000000000', reason: 'nil UUID (may be invalid in context)' },
];
for (const { value, reason } of invalidUuids) {
cases.push({
name: `${field.name}: should reject invalid UUID -- ${reason}`,
field: field.name,
category: 'format_violation',
input: value,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
},
});
}
}
if (field.type === 'enum' && field.enumValues) {
cases.push({
name: `${field.name}: should reject value not in enum`,
field: field.name,
category: 'format_violation',
input: 'INVALID_ENUM_VALUE',
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
messagePattern: new RegExp(`${field.name}.*(?:invalid|must be one of|enum)`, 'i'),
},
});
// Case sensitivity
if (field.enumValues.length > 0) {
const firstValue = field.enumValues[0];
cases.push({
name: `${field.name}: should reject wrong-case enum value`,
field: field.name,
category: 'format_violation',
input: firstValue === firstValue.toLowerCase() ? firstValue.toUpperCase() : firstValue.toLowerCase(),
expectedError: { status: 400, code: 'VALIDATION_ERROR' },
});
}
}
return cases;
}// injection-payloads.ts
function generateInjectionPayloads(field: FieldDefinition): NegativeTestCase[] {
const cases: NegativeTestCase[] = [];
if (field.type === 'string' || field.type === 'email' || field.type === 'url') {
const injectionStrings = [
// SQL injection
{ value: "'; DROP TABLE users; --", category: 'SQL injection' },
{ value: "' OR '1'='1", category: 'SQL injection' },
{ value: "' UNION SELECT * FROM passwords --", category: 'SQL injection' },
{ value: "1; UPDATE users SET role='admin' WHERE '1'='1", category: 'SQL injection' },
// XSS
{ value: '<script>alert("XSS")</script>', category: 'XSS' },
{ value: '<img src=x onerror=alert(1)>', category: 'XSS' },
{ value: '"><script>alert(document.cookie)</script>', category: 'XSS' },
{ value: "javascript:alert('XSS')", category: 'XSS' },
{ value: '<svg onload=alert(1)>', category: 'XSS' },
// Path traversal
{ value: '../../../etc/passwd', category: 'Path traversal' },
{ value: '..\\..\\..\\windows\\system32\\config\\sam', category: 'Path traversal' },
{ value: '/etc/shadow', category: 'Path traversal' },
// Command injection
{ value: '; ls -la', category: 'Command injection' },
{ value: '| cat /etc/passwd', category: 'Command injection' },
{ value: '$(whoami)', category: 'Command injection' },
{ value: '`id`', category: 'Command injection' },
// LDAP injection
{ value: '*)(objectClass=*)', category: 'LDAP injection' },
// XML injection
{ value: '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>', category: 'XXE' },
// Header injection
{ value: 'value\r\nInjected-Header: malicious', category: 'Header injection' },
// Unicode and encoding attacks
{ value: '\u0000null_byte', category: 'Null byte injection' },
{ value: '%00null_byte', category: 'URL-encoded null byte' },
{ value: '\uFEFFBOM_prefix', category: 'BOM injection' },
];
for (const { value, category } of injectionStrings) {
cases.push({
name: `${field.name}: should safely handle ${category} attempt`,
field: field.name,
category: 'injection',
input: value,
expectedError: {
status: 400,
code: 'VALIDATION_ERROR',
// The response should NOT contain the injected content reflected back
},
});
}
}
return cases;
}// create-user.negative.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
const API_BASE = process.env.API_BASE_URL || 'http://localhost:3000/api';
// Define the schema for test generation
const userFields: FieldDefinition[] = [
{ name: 'email', type: 'email', required: true, maxLength: 255 },
{ name: 'name', type: 'string', required: true, minLength: 1, maxLength: 100 },
{ name: 'age', type: 'number', required: false, min: 0, max: 150 },
{ name: 'role', type: 'enum', required: true, enumValues: ['admin', 'user', 'moderator'] },
{ name: 'website', type: 'url', required: false, maxLength: 2048 },
{ name: 'birthDate', type: 'date', required: false },
];
const validUser = {
email: '[email protected]',
name: 'Test User',
age: 25,
role: 'user',
website: 'https://example.com',
birthDate: '1998-06-15',
};
describe('POST /api/users -- Negative Tests', () => {
// Generate and run type violation tests for every field
for (const field of userFields) {
const typeViolations = generateTypeViolations(field);
describe(`${field.name} -- type violations`, () => {
for (const testCase of typeViolations) {
it(testCase.name, async () => {
const payload = { ...validUser, [field.name]: testCase.input };
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
expect(response.status).toBe(testCase.expectedError.status);
const body = await response.json();
expect(body.code || body.error).toBeDefined();
// Verify error does not leak internal details
const bodyText = JSON.stringify(body);
expect(bodyText).not.toMatch(/stack|trace|node_modules|internal/i);
});
}
});
}
// Missing required fields
describe('Missing required fields', () => {
const missingFieldCases = generateMissingFieldCases(userFields);
for (const testCase of missingFieldCases) {
it(testCase.name, async () => {
const payload = { ...validUser };
if (testCase.input === undefined) {
delete (payload as any)[testCase.field];
} else {
(payload as any)[testCase.field] = testCase.input;
}
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
expect(response.status).toBe(400);
});
}
});
// Boundary violations
describe('Boundary violations', () => {
for (const field of userFields) {
const boundaryViolations = generateBoundaryViolations(field);
for (const testCase of boundaryViolations) {
it(testCase.name, async () => {
const payload = { ...validUser, [field.name]: testCase.input };
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
expect(response.status).toBe(400);
});
}
}
});
// Format violations
describe('Format violations', () => {
for (const field of userFields) {
const formatViolations = generateFormatViolations(field);
for (const testCase of formatViolations) {
it(testCase.name, async () => {
const payload = { ...validUser, [field.name]: testCase.input };
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
expect(response.status).toBe(400);
});
}
}
});
// Injection attempts
describe('Injection attempts', () => {
for (const field of userFields) {
const injectionCases = generateInjectionPayloads(field);
for (const testCase of injectionCases) {
it(testCase.name, async () => {
const payload = { ...validUser, [field.name]: testCase.input };
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
// Should either reject (400) or sanitize (200 with sanitized data)
expect(response.status).toBeOneOf([400, 200, 422]);
// If accepted, verify the injection is not reflected back unsanitized
if (response.status === 200) {
const body = await response.json();
const bodyStr = JSON.stringify(body);
expect(bodyStr).not.toContain('<script>');
expect(bodyStr).not.toContain('DROP TABLE');
}
});
}
}
});
});// malformed-json.negative.test.ts
describe('Malformed Request Handling', () => {
describe('Invalid JSON body', () => {
it('should reject malformed JSON', async () => {
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{"email": "[email protected]",}', // trailing comma
});
expect(response.status).toBe(400);
const body = await response.json();
expect(body.message || body.error).toMatch(/json|parse|syntax/i);
});
it('should reject truncated JSON', async () => {
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{"email": "test@exam',
});
expect(response.status).toBe(400);
});
it('should reject empty body', async () => {
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '',
});
expect(response.status).toBe(400);
});
it('should reject non-JSON content type with JSON body', async () => {
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify(validUser),
});
expect(response.status).toBeOneOf([400, 415]); // 415 Unsupported Media Type
});
it('should reject body with duplicate keys', async () => {
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{"email": "[email protected]", "email": "[email protected]", "name": "Test", "role": "user"}',
});
// At minimum, the last value should not silently override
if (response.status === 200) {
const body = await response.json();
// Document which value wins
expect(body.email).toBeDefined();
}
});
});
describe('Oversized payloads', () => {
it('should reject payload exceeding size limit', async () => {
const oversizedPayload = {
...validUser,
name: 'x'.repeat(10 * 1024 * 1024), // 10MB string
};
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(oversizedPayload),
});
expect(response.status).toBeOneOf([400, 413]); // 413 Payload Too Large
});
it('should reject deeply nested JSON', async () => {
let nested: any = { value: 'deep' };
for (let i = 0; i < 100; i++) {
nested = { child: nested };
}
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...validUser, metadata: nested }),
});
expect(response.status).toBeOneOf([400, 413, 422]);
});
it('should reject payload with excessive array elements', async () => {
const response = await fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...validUser,
tags: Array.from({ length: 100000 }, (_, i) => `tag_${i}`),
}),
});
expect(response.status).toBeOneOf([400, 413, 422]);
});
});
});// authentication.negative.test.ts
describe('Authentication Negative Tests', () => {
it('should reject request with no auth token', async () => {
const response = await fetch(`${API_BASE}/users/me`, {
headers: { 'Content-Type': 'application/json' },
});
expect(response.status).toBe(401);
const body = await response.json();
expect(body.message || body.error).toMatch(/auth|token|unauthorized/i);
});
it('should reject request with expired token', async () => {
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNjAwMDAwMDAwfQ.invalid';
const response = await fetch(`${API_BASE}/users/me`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${expiredToken}`,
},
});
expect(response.status).toBe(401);
});
it('should reject request with malformed token', async () => {
const response = await fetch(`${API_BASE}/users/me`, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer not.a.valid.jwt',
},
});
expect(response.status).toBe(401);
});
it('should reject request with token signed by wrong key', async () => {
const wrongKeyToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const response = await fetch(`${API_BASE}/users/me`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${wrongKeyToken}`,
},
});
expect(response.status).toBe(401);
});
it('should reject non-Bearer auth scheme', async () => {
const response = await fetch(`${API_BASE}/users/me`, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic dXNlcjpwYXNz',
},
});
expect(response.status).toBeOneOf([401, 403]);
});
});
// authorization.negative.test.ts
describe('Authorization Negative Tests', () => {
it('should reject user accessing another user profile', async () => {
const response = await fetch(`${API_BASE}/users/other-user-id`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${regularUserToken}`,
},
});
expect(response.status).toBe(403);
});
it('should reject non-admin accessing admin endpoints', async () => {
const response = await fetch(`${API_BASE}/admin/users`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${regularUserToken}`,
},
});
expect(response.status).toBe(403);
});
it('should reject deleted user token', async () => {
const response = await fetch(`${API_BASE}/users/me`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${deletedUserToken}`,
},
});
expect(response.status).toBeOneOf([401, 403]);
});
});// race-condition.negative.test.ts
describe('Concurrent Operation Conflicts', () => {
it('should handle duplicate creation requests', async () => {
const user = { email: '[email protected]', name: 'Race Test', role: 'user' };
const [response1, response2] = await Promise.all([
fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
}),
fetch(`${API_BASE}/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
}),
]);
const statuses = [response1.status, response2.status].sort();
// One should succeed (201), the other should fail (409 Conflict)
expect(statuses).toContain(201);
expect(statuses).toContain(409);
});
it('should handle concurrent updates with optimistic locking', async () => {
// Create a resource first
const createResponse = await fetch(`${API_BASE}/orders`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify({ items: [{ id: 'i1', quantity: 1 }] }),
});
const { id, version } = await createResponse.json();
// Two concurrent updates with the same version
const [update1, update2] = await Promise.all([
fetch(`${API_BASE}/orders/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'If-Match': version,
},
body: JSON.stringify({ status: 'confirmed' }),
}),
fetch(`${API_BASE}/orders/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'If-Match': version,
},
body: JSON.stringify({ status: 'cancelled' }),
}),
]);
const statuses = [update1.status, update2.status].sort();
expect(statuses).toContain(200);
expect(statuses).toContain(409); // Optimistic locking conflict
});
});// negative-test.config.ts
interface NegativeTestConfig {
generation: {
includeTypeViolations: boolean;
includeMissingFields: boolean;
includeBoundaryViolations: boolean;
includeFormatViolations: boolean;
includeInjectionPayloads: boolean;
includeConcurrencyTests: boolean;
customInjectionPayloadsPath?: string;
};
execution: {
apiBaseUrl: string;
authToken?: string;
timeoutMs: number;
retryCount: number;
parallelRequests: number;
};
validation: {
requireActionableErrorMessages: boolean;
forbidInternalDetailsInErrors: boolean;
maxResponseTimeMs: number;
expectedErrorFormat: 'rfc7807' | 'custom' | 'any';
};
reporting: {
outputDirectory: string;
format: 'json' | 'markdown' | 'junit';
includePayloads: boolean;
includeResponseBodies: boolean;
};
}
const defaultConfig: NegativeTestConfig = {
generation: {
includeTypeViolations: true,
includeMissingFields: true,
includeBoundaryViolations: true,
includeFormatViolations: true,
includeInjectionPayloads: true,
includeConcurrencyTests: true,
},
execution: {
apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:3000/api',
timeoutMs: 10000,
retryCount: 0,
parallelRequests: 5,
},
validation: {
requireActionableErrorMessages: true,
forbidInternalDetailsInErrors: true,
maxResponseTimeMs: 5000,
expectedErrorFormat: 'any',
},
reporting: {
outputDirectory: './test-artifacts/negative-tests',
format: 'json',
includePayloads: true,
includeResponseBodies: true,
},
};{ error: "message" } and another returns { message: "error" }, that inconsistency is a finding..safeParse() and inspect .error.issues[].path.express.json({ limit: '1mb' })) or equivalent middleware configuration.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.