Boundary Value Generator — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Boundary Value 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 boundary value analysis (BVA) and equivalence class partitioning. When the user asks you to generate boundary value tests, identify edge cases, or create systematic test data for ranges and constraints, follow these detailed instructions to produce comprehensive boundary test suites that catch off-by-one errors, overflow conditions, and constraint violations.
tests/
boundary/
numeric/
integer-ranges.test.ts
float-precision.test.ts
currency-amounts.test.ts
percentage-values.test.ts
string/
length-limits.test.ts
unicode-boundaries.test.ts
encoding-limits.test.ts
date-time/
date-ranges.test.ts
time-zones.test.ts
epoch-boundaries.test.ts
collection/
array-sizes.test.ts
pagination.test.ts
batch-limits.test.ts
file/
file-size-limits.test.ts
upload-constraints.test.ts
api/
rate-limits.test.ts
payload-sizes.test.ts
concurrent-connections.test.ts
generators/
boundary-generator.ts
equivalence-partitioner.ts
test-case-formatter.ts
fixtures/
constraint-definitions.ts
type-boundaries.ts
vitest.config.ts// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['tests/boundary/**/*.test.ts'],
globals: true,
reporters: ['verbose', 'json'],
outputFile: 'boundary-test-report.json',
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/*.d.ts'],
},
},
});// tests/boundary/fixtures/constraint-definitions.ts
/**
* Centralized constraint definitions for all boundary-testable inputs.
* Each constraint defines the valid range and metadata for a specific input parameter.
*/
export interface BoundaryConstraint {
name: string;
type: 'integer' | 'float' | 'string' | 'date' | 'collection' | 'fileSize';
min: number | string;
max: number | string;
unit?: string;
description: string;
}
export const constraints: BoundaryConstraint[] = [
// Numeric constraints
{ name: 'user-age', type: 'integer', min: 13, max: 120, unit: 'years', description: 'User age for registration' },
{ name: 'quantity', type: 'integer', min: 1, max: 99, unit: 'items', description: 'Product quantity in cart' },
{ name: 'price', type: 'float', min: 0.01, max: 999999.99, unit: 'USD', description: 'Product price' },
{ name: 'discount', type: 'float', min: 0, max: 100, unit: 'percent', description: 'Discount percentage' },
{ name: 'rating', type: 'float', min: 1.0, max: 5.0, unit: 'stars', description: 'Product rating' },
// String constraints
{ name: 'username', type: 'string', min: 3, max: 30, unit: 'characters', description: 'Username length' },
{ name: 'password', type: 'string', min: 8, max: 128, unit: 'characters', description: 'Password length' },
{ name: 'bio', type: 'string', min: 0, max: 500, unit: 'characters', description: 'User biography' },
{ name: 'search-query', type: 'string', min: 1, max: 200, unit: 'characters', description: 'Search input' },
// Collection constraints
{ name: 'cart-items', type: 'collection', min: 0, max: 50, unit: 'items', description: 'Shopping cart item count' },
{ name: 'tags', type: 'collection', min: 0, max: 10, unit: 'tags', description: 'Tags per item' },
{ name: 'page-size', type: 'integer', min: 1, max: 100, unit: 'results', description: 'Pagination page size' },
// File constraints
{ name: 'avatar', type: 'fileSize', min: 1, max: 5242880, unit: 'bytes', description: 'Avatar file size (5MB max)' },
{ name: 'document', type: 'fileSize', min: 1, max: 26214400, unit: 'bytes', description: 'Document upload (25MB max)' },
];// tests/boundary/fixtures/type-boundaries.ts
/**
* Language-level type boundaries for JavaScript/TypeScript.
* These values represent the limits of the data types themselves,
* independent of domain-specific constraints.
*/
export const TYPE_BOUNDARIES = {
integer: {
MAX_SAFE_INTEGER: Number.MAX_SAFE_INTEGER, // 9007199254740991
MIN_SAFE_INTEGER: Number.MIN_SAFE_INTEGER, // -9007199254740991
MAX_VALUE: Number.MAX_VALUE, // 1.7976931348623157e+308
MIN_VALUE: Number.MIN_VALUE, // 5e-324 (smallest positive)
POSITIVE_INFINITY: Number.POSITIVE_INFINITY,
NEGATIVE_INFINITY: Number.NEGATIVE_INFINITY,
NaN: Number.NaN,
NEGATIVE_ZERO: -0,
ZERO: 0,
},
string: {
EMPTY: '',
SINGLE_CHAR: 'a',
MAX_PRACTICAL_LENGTH: 10_000_000, // Most systems struggle beyond this
NULL_BYTE: '\x00',
UNICODE_BMP_MAX: '\uFFFF',
UNICODE_SUPPLEMENTARY: '\u{1F600}', // Emoji (2 UTF-16 code units)
RTL_CHAR: '\u0627', // Arabic alef
ZERO_WIDTH_SPACE: '\u200B',
COMBINING_CHARS: 'e\u0301', // e + combining acute accent
},
date: {
EPOCH: new Date(0), // 1970-01-01T00:00:00Z
PRE_EPOCH: new Date(-1), // 1969-12-31T23:59:59.999Z
Y2K: new Date('2000-01-01T00:00:00Z'),
Y2K38: new Date('2038-01-19T03:14:07Z'), // Unix 32-bit overflow
FAR_FUTURE: new Date('9999-12-31T23:59:59Z'),
LEAP_DAY: new Date('2024-02-29T00:00:00Z'),
DST_SPRING: new Date('2024-03-10T02:00:00'), // US DST spring forward
DST_FALL: new Date('2024-11-03T02:00:00'), // US DST fall back
INVALID: new Date('invalid'),
},
};The core of boundary value analysis is systematically generating test values at and around the edges of valid ranges.
// tests/boundary/generators/boundary-generator.ts
export interface BoundaryTestCase {
value: number | string;
expected: 'valid' | 'invalid';
category: 'below-min' | 'at-min' | 'above-min' | 'nominal' | 'below-max' | 'at-max' | 'above-max' | 'type-boundary';
description: string;
}
/**
* Generate boundary value test cases for an integer range.
* Applies the BVA triplet pattern: boundary, boundary-1, boundary+1.
*/
export function generateIntegerBoundaries(
min: number,
max: number,
name: string
): BoundaryTestCase[] {
return [
// Below minimum boundary
{ value: min - 2, expected: 'invalid', category: 'below-min', description: `${name}: far below minimum (${min - 2})` },
{ value: min - 1, expected: 'invalid', category: 'below-min', description: `${name}: just below minimum (${min - 1})` },
// Minimum boundary
{ value: min, expected: 'valid', category: 'at-min', description: `${name}: exactly at minimum (${min})` },
{ value: min + 1, expected: 'valid', category: 'above-min', description: `${name}: just above minimum (${min + 1})` },
// Nominal value
{ value: Math.floor((min + max) / 2), expected: 'valid', category: 'nominal', description: `${name}: nominal mid-range` },
// Maximum boundary
{ value: max - 1, expected: 'valid', category: 'below-max', description: `${name}: just below maximum (${max - 1})` },
{ value: max, expected: 'valid', category: 'at-max', description: `${name}: exactly at maximum (${max})` },
// Above maximum boundary
{ value: max + 1, expected: 'invalid', category: 'above-max', description: `${name}: just above maximum (${max + 1})` },
{ value: max + 2, expected: 'invalid', category: 'above-max', description: `${name}: far above maximum (${max + 2})` },
// Type boundaries
{ value: 0, expected: min > 0 ? 'invalid' : 'valid', category: 'type-boundary', description: `${name}: zero` },
{ value: -1, expected: min > -1 ? 'invalid' : 'valid', category: 'type-boundary', description: `${name}: negative one` },
{ value: Number.MAX_SAFE_INTEGER, expected: max < Number.MAX_SAFE_INTEGER ? 'invalid' : 'valid', category: 'type-boundary', description: `${name}: MAX_SAFE_INTEGER` },
];
}
/**
* Generate boundary values for floating-point ranges.
* Includes precision-sensitive values around the boundaries.
*/
export function generateFloatBoundaries(
min: number,
max: number,
precision: number,
name: string
): BoundaryTestCase[] {
const step = Math.pow(10, -precision); // e.g., 0.01 for 2 decimal places
return [
// Below minimum
{ value: parseFloat((min - step).toFixed(precision)), expected: 'invalid', category: 'below-min', description: `${name}: one step below minimum` },
// Minimum boundary
{ value: min, expected: 'valid', category: 'at-min', description: `${name}: exactly at minimum (${min})` },
{ value: parseFloat((min + step).toFixed(precision)), expected: 'valid', category: 'above-min', description: `${name}: one step above minimum` },
// Nominal
{ value: parseFloat(((min + max) / 2).toFixed(precision)), expected: 'valid', category: 'nominal', description: `${name}: nominal mid-range` },
// Maximum boundary
{ value: parseFloat((max - step).toFixed(precision)), expected: 'valid', category: 'below-max', description: `${name}: one step below maximum` },
{ value: max, expected: 'valid', category: 'at-max', description: `${name}: exactly at maximum (${max})` },
// Above maximum
{ value: parseFloat((max + step).toFixed(precision)), expected: 'invalid', category: 'above-max', description: `${name}: one step above maximum` },
// Floating-point precision edge cases
{ value: 0.1 + 0.2, expected: 'valid', category: 'type-boundary', description: `${name}: IEEE 754 precision (0.1 + 0.2 = ${0.1 + 0.2})` },
{ value: Number.EPSILON, expected: min <= Number.EPSILON ? 'valid' : 'invalid', category: 'type-boundary', description: `${name}: Number.EPSILON` },
];
}
/**
* Generate boundary values for string length constraints.
*/
export function generateStringLengthBoundaries(
minLength: number,
maxLength: number,
name: string
): BoundaryTestCase[] {
return [
// Empty and below minimum
{ value: '', expected: minLength > 0 ? 'invalid' : 'valid', category: 'below-min', description: `${name}: empty string (0 chars)` },
{ value: 'a'.repeat(Math.max(0, minLength - 1)), expected: minLength > 0 ? 'invalid' : 'valid', category: 'below-min', description: `${name}: ${Math.max(0, minLength - 1)} characters` },
// Minimum boundary
{ value: 'a'.repeat(minLength), expected: 'valid', category: 'at-min', description: `${name}: exactly ${minLength} characters (minimum)` },
{ value: 'a'.repeat(minLength + 1), expected: 'valid', category: 'above-min', description: `${name}: ${minLength + 1} characters` },
// Nominal
{ value: 'a'.repeat(Math.floor((minLength + maxLength) / 2)), expected: 'valid', category: 'nominal', description: `${name}: mid-range length` },
// Maximum boundary
{ value: 'a'.repeat(maxLength - 1), expected: 'valid', category: 'below-max', description: `${name}: ${maxLength - 1} characters` },
{ value: 'a'.repeat(maxLength), expected: 'valid', category: 'at-max', description: `${name}: exactly ${maxLength} characters (maximum)` },
// Above maximum
{ value: 'a'.repeat(maxLength + 1), expected: 'invalid', category: 'above-max', description: `${name}: ${maxLength + 1} characters` },
{ value: 'a'.repeat(maxLength + 100), expected: 'invalid', category: 'above-max', description: `${name}: significantly over maximum` },
];
}Using the generator to produce actual test suites.
// tests/boundary/numeric/integer-ranges.test.ts
import { describe, it, expect } from 'vitest';
import { generateIntegerBoundaries } from '../generators/boundary-generator';
import { constraints } from '../fixtures/constraint-definitions';
// Example: testing a validation function
function validateAge(age: number): { valid: boolean; error?: string } {
if (!Number.isInteger(age)) return { valid: false, error: 'Age must be a whole number' };
if (age < 13) return { valid: false, error: 'Must be at least 13 years old' };
if (age > 120) return { valid: false, error: 'Age exceeds maximum allowed value' };
return { valid: true };
}
function validateQuantity(qty: number): { valid: boolean; error?: string } {
if (!Number.isInteger(qty)) return { valid: false, error: 'Quantity must be a whole number' };
if (qty < 1) return { valid: false, error: 'Quantity must be at least 1' };
if (qty > 99) return { valid: false, error: 'Maximum quantity is 99' };
return { valid: true };
}
describe('Integer Range Boundaries', () => {
describe('User Age Validation', () => {
const ageConstraint = constraints.find((c) => c.name === 'user-age')!;
const testCases = generateIntegerBoundaries(
ageConstraint.min as number,
ageConstraint.max as number,
'age'
);
for (const tc of testCases) {
it(`${tc.description} -> ${tc.expected}`, () => {
const result = validateAge(tc.value as number);
if (tc.expected === 'valid') {
expect(result.valid).toBe(true);
expect(result.error).toBeUndefined();
} else {
expect(result.valid).toBe(false);
expect(result.error).toBeDefined();
expect(result.error!.length).toBeGreaterThan(0);
}
});
}
// Additional type boundary tests
it('rejects NaN', () => {
const result = validateAge(NaN);
expect(result.valid).toBe(false);
});
it('rejects Infinity', () => {
const result = validateAge(Infinity);
expect(result.valid).toBe(false);
});
it('rejects floating-point numbers', () => {
const result = validateAge(25.5);
expect(result.valid).toBe(false);
});
it('rejects negative zero', () => {
const result = validateAge(-0);
// -0 is technically 0, which is below minimum
expect(result.valid).toBe(false);
});
});
describe('Product Quantity Validation', () => {
const qtyConstraint = constraints.find((c) => c.name === 'quantity')!;
const testCases = generateIntegerBoundaries(
qtyConstraint.min as number,
qtyConstraint.max as number,
'quantity'
);
for (const tc of testCases) {
it(`${tc.description} -> ${tc.expected}`, () => {
const result = validateQuantity(tc.value as number);
expect(result.valid).toBe(tc.expected === 'valid');
});
}
});
});Financial calculations require special boundary attention due to IEEE 754 floating-point precision.
// tests/boundary/numeric/currency-amounts.test.ts
import { describe, it, expect } from 'vitest';
import { generateFloatBoundaries } from '../generators/boundary-generator';
function validatePrice(price: number): { valid: boolean; error?: string } {
if (typeof price !== 'number' || isNaN(price)) {
return { valid: false, error: 'Price must be a number' };
}
if (!isFinite(price)) {
return { valid: false, error: 'Price must be finite' };
}
if (price < 0.01) {
return { valid: false, error: 'Price must be at least $0.01' };
}
if (price > 999999.99) {
return { valid: false, error: 'Price exceeds maximum' };
}
// Check for more than 2 decimal places
const decimalStr = price.toString();
const decimalPart = decimalStr.includes('.') ? decimalStr.split('.')[1] : '';
if (decimalPart.length > 2) {
return { valid: false, error: 'Price must have at most 2 decimal places' };
}
return { valid: true };
}
describe('Currency Amount Boundaries', () => {
const testCases = generateFloatBoundaries(0.01, 999999.99, 2, 'price');
for (const tc of testCases) {
it(`${tc.description} -> ${tc.expected}`, () => {
const result = validatePrice(tc.value as number);
expect(result.valid).toBe(tc.expected === 'valid');
});
}
describe('Floating-point precision edge cases', () => {
it('handles 0.1 + 0.2 correctly', () => {
// 0.1 + 0.2 = 0.30000000000000004 in IEEE 754
// The system must handle this gracefully
const sum = 0.1 + 0.2;
const result = validatePrice(parseFloat(sum.toFixed(2)));
expect(result.valid).toBe(true);
});
it('handles currency multiplication precision', () => {
// $19.99 * 3 = 59.97, but floating-point may produce 59.96999...
const total = 19.99 * 3;
const result = validatePrice(parseFloat(total.toFixed(2)));
expect(result.valid).toBe(true);
});
it('rejects amounts with more than 2 decimal places', () => {
expect(validatePrice(9.999).valid).toBe(false);
expect(validatePrice(0.001).valid).toBe(false);
expect(validatePrice(100.123).valid).toBe(false);
});
it('accepts exact boundary: $0.01', () => {
expect(validatePrice(0.01).valid).toBe(true);
});
it('accepts exact boundary: $999999.99', () => {
expect(validatePrice(999999.99).valid).toBe(true);
});
it('rejects $0.00', () => {
expect(validatePrice(0.00).valid).toBe(false);
});
it('rejects negative amounts', () => {
expect(validatePrice(-0.01).valid).toBe(false);
expect(validatePrice(-100).valid).toBe(false);
});
});
});Dates have unique boundary conditions: leap years, daylight saving time transitions, epoch boundaries, and the Y2K38 problem.
// tests/boundary/date-time/date-ranges.test.ts
import { describe, it, expect } from 'vitest';
import { TYPE_BOUNDARIES } from '../fixtures/type-boundaries';
function validateEventDate(date: Date): { valid: boolean; error?: string } {
if (!(date instanceof Date) || isNaN(date.getTime())) {
return { valid: false, error: 'Invalid date' };
}
const now = new Date();
const oneYearFromNow = new Date(now);
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);
if (date < now) {
return { valid: false, error: 'Event date must be in the future' };
}
if (date > oneYearFromNow) {
return { valid: false, error: 'Event date must be within one year' };
}
return { valid: true };
}
function validateBirthDate(date: Date): { valid: boolean; error?: string } {
if (!(date instanceof Date) || isNaN(date.getTime())) {
return { valid: false, error: 'Invalid date' };
}
const now = new Date();
const age = now.getFullYear() - date.getFullYear();
if (date > now) {
return { valid: false, error: 'Birth date cannot be in the future' };
}
if (age > 150) {
return { valid: false, error: 'Birth date too far in the past' };
}
return { valid: true };
}
describe('Date Range Boundaries', () => {
describe('Event Date Validation', () => {
it('rejects dates in the past', () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
expect(validateEventDate(yesterday).valid).toBe(false);
});
it('accepts tomorrow', () => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
expect(validateEventDate(tomorrow).valid).toBe(true);
});
it('accepts exactly one year from now', () => {
const oneYear = new Date();
oneYear.setFullYear(oneYear.getFullYear() + 1);
expect(validateEventDate(oneYear).valid).toBe(true);
});
it('rejects one year and one day from now', () => {
const beyondOneYear = new Date();
beyondOneYear.setFullYear(beyondOneYear.getFullYear() + 1);
beyondOneYear.setDate(beyondOneYear.getDate() + 1);
expect(validateEventDate(beyondOneYear).valid).toBe(false);
});
it('rejects invalid date object', () => {
expect(validateEventDate(TYPE_BOUNDARIES.date.INVALID).valid).toBe(false);
});
});
describe('Critical Date Boundaries', () => {
it('handles leap year boundary: Feb 29', () => {
const leapDay = new Date('2024-02-29T12:00:00Z');
expect(leapDay.getDate()).toBe(29);
expect(leapDay.getMonth()).toBe(1); // 0-indexed
});
it('handles non-leap year: Feb 28 to Mar 1', () => {
const feb28 = new Date('2023-02-28T23:59:59Z');
const mar1 = new Date(feb28.getTime() + 1000);
expect(mar1.getDate()).toBe(1);
expect(mar1.getMonth()).toBe(2); // March
});
it('handles epoch boundary', () => {
const result = validateBirthDate(TYPE_BOUNDARIES.date.EPOCH);
expect(result.valid).toBe(true);
});
it('handles pre-epoch date', () => {
const result = validateBirthDate(new Date('1969-12-31T23:59:59Z'));
expect(result.valid).toBe(true);
});
it('handles Y2K38 boundary', () => {
// Unix 32-bit timestamp overflow: 2038-01-19T03:14:07Z
const y2k38 = TYPE_BOUNDARIES.date.Y2K38;
expect(y2k38.getTime()).toBeGreaterThan(0);
});
it('handles far future date', () => {
const farFuture = TYPE_BOUNDARIES.date.FAR_FUTURE;
expect(farFuture.getFullYear()).toBe(9999);
});
it('handles month-end transitions', () => {
const monthEnds = [
new Date('2024-01-31T23:59:59Z'), // Jan 31 -> Feb 1
new Date('2024-03-31T23:59:59Z'), // Mar 31 -> Apr 1
new Date('2024-04-30T23:59:59Z'), // Apr 30 -> May 1
new Date('2024-12-31T23:59:59Z'), // Dec 31 -> Jan 1 (year transition)
];
for (const date of monthEnds) {
const nextSecond = new Date(date.getTime() + 1000);
expect(nextSecond.getDate()).toBe(1);
}
});
it('handles year-end transition', () => {
const yearEnd = new Date('2024-12-31T23:59:59Z');
const newYear = new Date(yearEnd.getTime() + 1000);
expect(newYear.getFullYear()).toBe(2025);
expect(newYear.getMonth()).toBe(0);
expect(newYear.getDate()).toBe(1);
});
});
});Arrays, lists, and other collections have their own boundary conditions related to size, indexing, and pagination.
// tests/boundary/collection/array-sizes.test.ts
import { describe, it, expect } from 'vitest';
function validateCartItems(items: unknown[]): { valid: boolean; error?: string } {
if (!Array.isArray(items)) {
return { valid: false, error: 'Items must be an array' };
}
if (items.length === 0) {
return { valid: false, error: 'Cart must have at least one item' };
}
if (items.length > 50) {
return { valid: false, error: 'Cart cannot exceed 50 items' };
}
return { valid: true };
}
function validateTags(tags: string[]): { valid: boolean; error?: string } {
if (!Array.isArray(tags)) {
return { valid: false, error: 'Tags must be an array' };
}
if (tags.length > 10) {
return { valid: false, error: 'Maximum 10 tags allowed' };
}
// Each tag has its own constraints
for (const tag of tags) {
if (tag.length < 1 || tag.length > 50) {
return { valid: false, error: 'Each tag must be 1-50 characters' };
}
}
return { valid: true };
}
describe('Collection Size Boundaries', () => {
describe('Cart Items', () => {
it('rejects empty cart', () => {
expect(validateCartItems([]).valid).toBe(false);
});
it('accepts single item (minimum)', () => {
expect(validateCartItems(['item1']).valid).toBe(true);
});
it('accepts two items (above minimum)', () => {
expect(validateCartItems(['item1', 'item2']).valid).toBe(true);
});
it('accepts 49 items (below maximum)', () => {
const items = Array.from({ length: 49 }, (_, i) => `item${i}`);
expect(validateCartItems(items).valid).toBe(true);
});
it('accepts 50 items (at maximum)', () => {
const items = Array.from({ length: 50 }, (_, i) => `item${i}`);
expect(validateCartItems(items).valid).toBe(true);
});
it('rejects 51 items (above maximum)', () => {
const items = Array.from({ length: 51 }, (_, i) => `item${i}`);
expect(validateCartItems(items).valid).toBe(false);
});
});
describe('Tags', () => {
it('accepts empty tags array', () => {
expect(validateTags([]).valid).toBe(true);
});
it('accepts single tag', () => {
expect(validateTags(['typescript']).valid).toBe(true);
});
it('accepts 10 tags (at maximum)', () => {
const tags = Array.from({ length: 10 }, (_, i) => `tag${i}`);
expect(validateTags(tags).valid).toBe(true);
});
it('rejects 11 tags (above maximum)', () => {
const tags = Array.from({ length: 11 }, (_, i) => `tag${i}`);
expect(validateTags(tags).valid).toBe(false);
});
it('rejects tag with 0 characters', () => {
expect(validateTags(['']).valid).toBe(false);
});
it('rejects tag with 51 characters', () => {
expect(validateTags(['a'.repeat(51)]).valid).toBe(false);
});
});
});Pagination has multiple boundary dimensions: page number, page size, total count, and offset calculations.
// tests/boundary/collection/pagination.test.ts
import { describe, it, expect } from 'vitest';
interface PaginationParams {
page: number;
pageSize: number;
totalItems: number;
}
interface PaginationResult {
valid: boolean;
error?: string;
offset?: number;
limit?: number;
totalPages?: number;
hasNext?: boolean;
hasPrevious?: boolean;
}
function calculatePagination(params: PaginationParams): PaginationResult {
const { page, pageSize, totalItems } = params;
if (!Number.isInteger(page) || page < 1) {
return { valid: false, error: 'Page must be a positive integer' };
}
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
return { valid: false, error: 'Page size must be between 1 and 100' };
}
if (totalItems < 0) {
return { valid: false, error: 'Total items cannot be negative' };
}
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
if (page > totalPages) {
return { valid: false, error: `Page ${page} exceeds total pages (${totalPages})` };
}
const offset = (page - 1) * pageSize;
return {
valid: true,
offset,
limit: pageSize,
totalPages,
hasNext: page < totalPages,
hasPrevious: page > 1,
};
}
describe('Pagination Boundaries', () => {
describe('Page number boundaries', () => {
it('accepts page 1 (minimum)', () => {
const result = calculatePagination({ page: 1, pageSize: 20, totalItems: 100 });
expect(result.valid).toBe(true);
expect(result.offset).toBe(0);
expect(result.hasPrevious).toBe(false);
});
it('rejects page 0', () => {
const result = calculatePagination({ page: 0, pageSize: 20, totalItems: 100 });
expect(result.valid).toBe(false);
});
it('rejects negative page', () => {
const result = calculatePagination({ page: -1, pageSize: 20, totalItems: 100 });
expect(result.valid).toBe(false);
});
it('accepts last page', () => {
const result = calculatePagination({ page: 5, pageSize: 20, totalItems: 100 });
expect(result.valid).toBe(true);
expect(result.hasNext).toBe(false);
expect(result.offset).toBe(80);
});
it('rejects page beyond last', () => {
const result = calculatePagination({ page: 6, pageSize: 20, totalItems: 100 });
expect(result.valid).toBe(false);
});
});
describe('Page size boundaries', () => {
it('accepts page size 1 (minimum)', () => {
const result = calculatePagination({ page: 1, pageSize: 1, totalItems: 100 });
expect(result.valid).toBe(true);
expect(result.totalPages).toBe(100);
});
it('accepts page size 100 (maximum)', () => {
const result = calculatePagination({ page: 1, pageSize: 100, totalItems: 100 });
expect(result.valid).toBe(true);
expect(result.totalPages).toBe(1);
});
it('rejects page size 0', () => {
const result = calculatePagination({ page: 1, pageSize: 0, totalItems: 100 });
expect(result.valid).toBe(false);
});
it('rejects page size 101', () => {
const result = calculatePagination({ page: 1, pageSize: 101, totalItems: 100 });
expect(result.valid).toBe(false);
});
});
describe('Total items edge cases', () => {
it('handles zero total items', () => {
const result = calculatePagination({ page: 1, pageSize: 20, totalItems: 0 });
expect(result.valid).toBe(true);
expect(result.totalPages).toBe(1);
});
it('handles single item', () => {
const result = calculatePagination({ page: 1, pageSize: 20, totalItems: 1 });
expect(result.valid).toBe(true);
expect(result.totalPages).toBe(1);
});
it('handles exact page boundary (100 items, 20 per page = 5 pages)', () => {
const result = calculatePagination({ page: 5, pageSize: 20, totalItems: 100 });
expect(result.valid).toBe(true);
expect(result.totalPages).toBe(5);
expect(result.hasNext).toBe(false);
});
it('handles one over page boundary (101 items = 6 pages)', () => {
const result = calculatePagination({ page: 6, pageSize: 20, totalItems: 101 });
expect(result.valid).toBe(true);
expect(result.totalPages).toBe(6);
});
});
});API rate limits are a critical boundary that affects system behavior under load.
// tests/boundary/api/rate-limits.test.ts
import { describe, it, expect } from 'vitest';
interface RateLimitConfig {
maxRequests: number;
windowMs: number;
}
interface RateLimitResult {
allowed: boolean;
remaining: number;
resetAt: number;
retryAfterMs?: number;
}
class RateLimiter {
private requests: number[] = [];
constructor(private config: RateLimitConfig) {}
check(now: number = Date.now()): RateLimitResult {
// Remove expired requests
const windowStart = now - this.config.windowMs;
this.requests = this.requests.filter((t) => t > windowStart);
if (this.requests.length >= this.config.maxRequests) {
const oldestRequest = this.requests[0];
const resetAt = oldestRequest + this.config.windowMs;
return {
allowed: false,
remaining: 0,
resetAt,
retryAfterMs: resetAt - now,
};
}
this.requests.push(now);
return {
allowed: true,
remaining: this.config.maxRequests - this.requests.length,
resetAt: now + this.config.windowMs,
};
}
reset(): void {
this.requests = [];
}
}
describe('API Rate Limit Boundaries', () => {
const config: RateLimitConfig = { maxRequests: 100, windowMs: 60000 };
it('allows request 1 (first request)', () => {
const limiter = new RateLimiter(config);
const result = limiter.check();
expect(result.allowed).toBe(true);
expect(result.remaining).toBe(99);
});
it('allows request 99 (one below limit)', () => {
const limiter = new RateLimiter(config);
const now = Date.now();
for (let i = 0; i < 98; i++) {
limiter.check(now + i);
}
const result = limiter.check(now + 98);
expect(result.allowed).toBe(true);
expect(result.remaining).toBe(1);
});
it('allows request 100 (at limit)', () => {
const limiter = new RateLimiter(config);
const now = Date.now();
for (let i = 0; i < 99; i++) {
limiter.check(now + i);
}
const result = limiter.check(now + 99);
expect(result.allowed).toBe(true);
expect(result.remaining).toBe(0);
});
it('rejects request 101 (above limit)', () => {
const limiter = new RateLimiter(config);
const now = Date.now();
for (let i = 0; i < 100; i++) {
limiter.check(now + i);
}
const result = limiter.check(now + 100);
expect(result.allowed).toBe(false);
expect(result.remaining).toBe(0);
expect(result.retryAfterMs).toBeGreaterThan(0);
});
it('allows requests after window resets', () => {
const limiter = new RateLimiter(config);
const now = Date.now();
// Fill the window
for (let i = 0; i < 100; i++) {
limiter.check(now + i);
}
// After the window passes, requests should be allowed again
const afterWindow = now + config.windowMs + 1;
const result = limiter.check(afterWindow);
expect(result.allowed).toBe(true);
});
});For Python teams using pytest, boundary generation follows the same systematic pattern.
# tests/boundary/generators/boundary_generator.py
from dataclasses import dataclass
from typing import Union, Literal
import math
@dataclass
class BoundaryTestCase:
value: Union[int, float, str]
expected: Literal["valid", "invalid"]
category: str
description: str
def generate_integer_boundaries(
min_val: int, max_val: int, name: str
) -> list[BoundaryTestCase]:
"""Generate boundary value test cases for an integer range."""
return [
BoundaryTestCase(min_val - 2, "invalid", "below-min", f"{name}: far below min ({min_val - 2})"),
BoundaryTestCase(min_val - 1, "invalid", "below-min", f"{name}: just below min ({min_val - 1})"),
BoundaryTestCase(min_val, "valid", "at-min", f"{name}: at minimum ({min_val})"),
BoundaryTestCase(min_val + 1, "valid", "above-min", f"{name}: just above min ({min_val + 1})"),
BoundaryTestCase((min_val + max_val) // 2, "valid", "nominal", f"{name}: mid-range"),
BoundaryTestCase(max_val - 1, "valid", "below-max", f"{name}: just below max ({max_val - 1})"),
BoundaryTestCase(max_val, "valid", "at-max", f"{name}: at maximum ({max_val})"),
BoundaryTestCase(max_val + 1, "invalid", "above-max", f"{name}: just above max ({max_val + 1})"),
BoundaryTestCase(max_val + 2, "invalid", "above-max", f"{name}: far above max ({max_val + 2})"),
BoundaryTestCase(0, "invalid" if min_val > 0 else "valid", "type-boundary", f"{name}: zero"),
BoundaryTestCase(-1, "invalid" if min_val > -1 else "valid", "type-boundary", f"{name}: negative one"),
]
def generate_string_length_boundaries(
min_len: int, max_len: int, name: str
) -> list[BoundaryTestCase]:
"""Generate boundary value test cases for string length constraints."""
return [
BoundaryTestCase("", "invalid" if min_len > 0 else "valid", "below-min", f"{name}: empty string"),
BoundaryTestCase("a" * max(0, min_len - 1), "invalid" if min_len > 0 else "valid", "below-min", f"{name}: {max(0, min_len - 1)} chars"),
BoundaryTestCase("a" * min_len, "valid", "at-min", f"{name}: {min_len} chars (minimum)"),
BoundaryTestCase("a" * (min_len + 1), "valid", "above-min", f"{name}: {min_len + 1} chars"),
BoundaryTestCase("a" * ((min_len + max_len) // 2), "valid", "nominal", f"{name}: mid-range"),
BoundaryTestCase("a" * (max_len - 1), "valid", "below-max", f"{name}: {max_len - 1} chars"),
BoundaryTestCase("a" * max_len, "valid", "at-max", f"{name}: {max_len} chars (maximum)"),
BoundaryTestCase("a" * (max_len + 1), "invalid", "above-max", f"{name}: {max_len + 1} chars"),
]# tests/boundary/test_numeric_boundaries.py
import pytest
from generators.boundary_generator import generate_integer_boundaries
def validate_age(age: int) -> tuple[bool, str]:
"""Validate user age for registration."""
if not isinstance(age, int):
return False, "Age must be an integer"
if age < 13:
return False, "Must be at least 13 years old"
if age > 120:
return False, "Age exceeds maximum"
return True, ""
class TestAgeBoundaries:
"""Parameterized boundary tests for user age validation."""
@pytest.fixture
def boundary_cases(self):
return generate_integer_boundaries(13, 120, "age")
@pytest.mark.parametrize(
"test_case",
generate_integer_boundaries(13, 120, "age"),
ids=lambda tc: tc.description,
)
def test_age_boundary(self, test_case):
is_valid, error = validate_age(test_case.value)
if test_case.expected == "valid":
assert is_valid, f"Expected valid for {test_case.value}, got error: {error}"
else:
assert not is_valid, f"Expected invalid for {test_case.value}"
def test_rejects_none(self):
is_valid, _ = validate_age(None)
assert not is_valid
def test_rejects_float(self):
is_valid, _ = validate_age(25.5)
assert not is_valid
def test_rejects_string(self):
is_valid, _ = validate_age("25")
assert not is_validFile uploads have both size boundaries and content-type boundaries.
// tests/boundary/file/file-size-limits.test.ts
import { describe, it, expect } from 'vitest';
interface FileValidationResult {
valid: boolean;
error?: string;
}
function validateFileUpload(
sizeBytes: number,
maxBytes: number,
allowedTypes: string[],
fileType: string
): FileValidationResult {
if (sizeBytes <= 0) {
return { valid: false, error: 'File is empty' };
}
if (sizeBytes > maxBytes) {
return { valid: false, error: `File exceeds maximum size of ${maxBytes} bytes` };
}
if (!allowedTypes.includes(fileType)) {
return { valid: false, error: `File type ${fileType} is not allowed` };
}
return { valid: true };
}
describe('File Size Boundaries', () => {
const MAX_AVATAR_SIZE = 5 * 1024 * 1024; // 5MB
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
it('rejects zero-byte file', () => {
const result = validateFileUpload(0, MAX_AVATAR_SIZE, allowedTypes, 'image/jpeg');
expect(result.valid).toBe(false);
});
it('accepts 1-byte file (minimum)', () => {
const result = validateFileUpload(1, MAX_AVATAR_SIZE, allowedTypes, 'image/jpeg');
expect(result.valid).toBe(true);
});
it('accepts file at exact maximum (5MB)', () => {
const result = validateFileUpload(MAX_AVATAR_SIZE, MAX_AVATAR_SIZE, allowedTypes, 'image/jpeg');
expect(result.valid).toBe(true);
});
it('rejects file one byte over maximum', () => {
const result = validateFileUpload(MAX_AVATAR_SIZE + 1, MAX_AVATAR_SIZE, allowedTypes, 'image/jpeg');
expect(result.valid).toBe(false);
});
it('accepts file one byte under maximum', () => {
const result = validateFileUpload(MAX_AVATAR_SIZE - 1, MAX_AVATAR_SIZE, allowedTypes, 'image/jpeg');
expect(result.valid).toBe(true);
});
it('rejects file at common misleading boundary (5,000,000 bytes is not 5MB)', () => {
// 5MB = 5,242,880 bytes, not 5,000,000
// Files between these values should still be accepted
const result = validateFileUpload(5_100_000, MAX_AVATAR_SIZE, allowedTypes, 'image/jpeg');
expect(result.valid).toBe(true);
});
});< instead of <=, or > instead of >=. The BVA triplet pattern (99, 100, 101) is designed to detect exactly this error.0.1 + 0.2 === 0.3 returns false in your boundary test, use a tolerance-based comparison: Math.abs(a - b) < Number.EPSILON or use toBeCloseTo() in Jest/Vitest instead of toBe().Intl.Segmenter for accurate grapheme counting or verify which length metric the validation uses.new Date('2024-01-01T00:00:00Z')) to eliminate time zone ambiguity.(page - 1) * pageSize versus page * pageSize shifts the entire result window.increment and compare matters.max + 1 wraps around to a negative number, the language or data type may have overflowed. In JavaScript, this occurs at Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 (both equal 9007199254740992). Use BigInt for values that might exceed safe integer range.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.