@absmartly/api-mocks
v1.0.8
Published
OpenAPI-driven mock server, typed factories, and schema validation for the ABsmartly API. Built on [MSW (Mock Service Worker)](https://mswjs.io/) with handlers auto-generated from the ABsmartly OpenAPI specification.
Readme
@absmartly/api-mocks
OpenAPI-driven mock server, typed factories, and schema validation for the ABsmartly API. Built on MSW (Mock Service Worker) with handlers auto-generated from the ABsmartly OpenAPI specification.
Installation
npm install --save-dev @absmartly/api-mocksPeer dependency: This package requires msw v2+:
npm install --save-dev mswQuick Start
import { createServer } from '@absmartly/api-mocks/server';
import { beforeAll, afterEach, afterAll, test, expect } from 'vitest';
const server = createServer('https://your-instance.absmartly.com/v1');
beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('fetches experiments', async () => {
const response = await fetch('https://your-instance.absmartly.com/v1/experiments');
const data = await response.json();
expect(response.ok).toBe(true);
});Package Exports
| Import path | Description |
|---|---|
| @absmartly/api-mocks | Everything: factories, handlers, validation, and createServer |
| @absmartly/api-mocks/server | createServer(baseUrl) -- creates a pre-configured MSW server |
| @absmartly/api-mocks/handlers | MSW request handlers (generated + error simulation) |
| @absmartly/api-mocks/factories | Faker-based factory functions for all API resources |
| @absmartly/api-mocks/validation | Schema validation utilities powered by AJV |
MSW Mock Server
createServer(baseUrl) returns an MSW setupServer instance with all generated handlers pre-registered.
import { createServer } from '@absmartly/api-mocks/server';
const server = createServer('https://your-instance.absmartly.com/v1');Vitest Setup
Create a setup file to start/stop the server around your tests:
// test/setup.ts
import { beforeAll, afterEach, afterAll } from 'vitest';
import { createServer } from '@absmartly/api-mocks/server';
const server = createServer('https://your-instance.absmartly.com/v1');
beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());Then reference it in your Vitest config:
// vitest.config.ts
export default defineConfig({
test: {
setupFiles: ['./test/setup.ts'],
},
});Pre-configured Server
The package also exports a pre-configured server instance that reads the base URL from environment variables:
import { server } from '@absmartly/api-mocks/src/mocks/server';The base URL is resolved in this order:
API_BASE_URLenvironment variableABSMARTLY_API_ENDPOINTenvironment variablehttps://sandbox.absmartly.com/v1(placeholder default — only used by MSW mocks, which intercept all requests before they reach the network)
Handlers
Generated Handlers
Handlers are auto-generated from the OpenAPI specification. Every endpoint in the spec gets a corresponding MSW handler that returns realistic example data.
import { createHandlers } from '@absmartly/api-mocks/handlers';
const handlers = createHandlers('https://your-instance.absmartly.com/v1');The generated handlers cover 40+ API resources including experiments, goals, metrics, segments, users, teams, applications, environments, and more.
How generated handlers work:
- GET (list) -- Returns the static example from the OpenAPI spec
- GET (by ID) -- Clones the example and sets the
idfield from the URL path parameter - POST / PUT / PATCH -- Clones the example, deep-merges the request body into the response, and validates the request using
withValidation() - DELETE -- Returns 204 No Content
The deep-merge strategy preserves response arrays (arrays from the example response take precedence over arrays in the request body), which ensures list fields always return realistic data.
Error Simulation Handlers
In addition to the generated handlers, the package provides error simulation handlers for testing error scenarios:
import { createErrorHandlers } from '@absmartly/api-mocks/handlers';
const errorHandlers = createErrorHandlers('https://your-instance.absmartly.com/v1');These handlers simulate common error cases:
| Endpoint | Trigger | Response |
|---|---|---|
| POST /experiments | Missing name | 400 -- name is required |
| POST /experiments | name: "duplicate_name" | 409 -- already exists |
| POST /experiments | Missing unit_type_id | 400 -- unit_type_id is required |
| PUT /experiments/999/start | ID = 999 | 400 -- already running |
| PUT /experiments/888/stop | ID = 888 | 400 -- not running |
| POST /goals | Missing name | 400 -- name is required |
| POST /goals | name: "duplicate" | 409 -- already exists |
| POST /segments | Missing name or value_source_attribute | 400 -- field is required |
| DELETE /segments/666 | ID = 666 | 409 -- in use by active experiments |
| POST /api_keys | Missing name | 400 -- name is required |
Factories
All factories follow the same pattern: createMock*(overrides?) returns a single instance, createMock*s(count) returns an array. Every factory is typed using the generated TypeScript types from the OpenAPI schema.
import { createMockExperiment, createMockExperiments } from '@absmartly/api-mocks/factories';
// Single instance with defaults
const experiment = createMockExperiment();
// With overrides
const experiment = createMockExperiment({
name: 'my_test',
state: 'running',
});
// Multiple instances
const experiments = createMockExperiments(5);Available Factories
| Factory | Type |
|---|---|
| createMockExperiment / createMockExperiments | ExperimentShort |
| createMockGoal / createMockGoals | Goal |
| createMockSegment / createMockSegments | Segment |
| createMockMetric / createMockMetrics | Metric |
| createMockUser / createMockUsers | User |
| createMockTeam / createMockTeams | Team |
| createMockApplication / createMockApplications | Application |
| createMockEnvironment / createMockEnvironments | Environment |
| createMockUnitType / createMockUnitTypes | UnitType |
| createMockApiKey / createMockApiKeys | ApiKey |
| createMockWebhook / createMockWebhooks | Webhook |
| createMockRole / createMockRoles | Role |
| createMockPermission / createMockPermissions | Permission |
| createMockPermissionCategory / createMockPermissionCategories | PermissionCategory |
| createMockMetricCategory / createMockMetricCategories | MetricCategory |
| createMockAlert / createMockAlerts | Alert |
| createMockNote / createMockNotes | Note |
| createMockExperimentTag / createMockExperimentTags | ExperimentTag |
| createMockGoalTag / createMockGoalTags | GoalTag |
| createMockMetricTag / createMockMetricTags | MetricTag |
Validation
The validation layer uses AJV to validate request and response payloads against the OpenAPI schema. All validation functions load and dereference the bundled OpenAPI spec automatically.
Request Validation (Lenient)
validateRequest is intentionally lenient -- it strips required, minItems, minProperties, and pattern constraints so you can send partial payloads in tests. It validates structure and types only.
import { validateRequest } from '@absmartly/api-mocks/validation';
const result = await validateRequest('/goals', 'POST', {
name: 'My Goal',
});
// result: { valid: true, errors: [] }Response Validation (Strict)
validateResponse applies the full schema including all constraints.
import { validateResponse } from '@absmartly/api-mocks/validation';
const result = await validateResponse('/goals', 'GET', 200, responseData);
if (!result.valid) {
console.log(result.errors);
// [{ path: '/items/0', message: '...', keyword: 'type' }]
}Strict Request/Response Validation
For when you want full constraint checking on requests too:
import { validateRequestStrict, validateResponseStrict } from '@absmartly/api-mocks/validation';
const result = await validateRequestStrict('/goals', 'POST', payload);findUnknownProperties can detect extra properties not defined in the schema:
import { findUnknownProperties, loadOpenAPISpec } from '@absmartly/api-mocks/validation';
const spec = await loadOpenAPISpec();
const unknowns = findUnknownProperties(data, schema, spec);Handler Validation Wrapper
withValidation wraps an MSW handler resolver to automatically validate incoming requests (and optionally responses):
import { withValidation } from '@absmartly/api-mocks/validation';
import { http, HttpResponse } from 'msw';
http.post(
`${baseUrl}/goals`,
withValidation('/goals', 'POST', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ ok: true, goal: body }, { status: 201 });
}, {
validateRequests: true, // default: true
validateResponses: false, // default: false
})
);When request validation fails, it returns a 422 response:
{
"ok": false,
"error": "validation_error",
"message": "Request validation failed",
"status": 422,
"details": [{ "path": "/name", "message": "must be string", "keyword": "type" }]
}Payload Generation
generatePayloadForEndpoint creates a minimal valid payload that satisfies the request schema for a given endpoint:
import { generatePayloadForEndpoint } from '@absmartly/api-mocks/validation';
const payload = await generatePayloadForEndpoint('/goals', 'POST', {
name: 'My Custom Goal',
});It generates sensible defaults for each type (string -> "test-string", integer -> 1, date-time -> "2025-01-01T00:00:00.000Z", email -> "[email protected]", etc.) and deep-merges any overrides you provide.
Schema Inspection
Lower-level functions for working with the OpenAPI spec directly:
import {
loadOpenAPISpec,
getSchemaForEndpoint,
getRequestSchemaForEndpoint,
createValidator,
validateAgainstSchema,
} from '@absmartly/api-mocks/validation';
// Load the full dereferenced spec
const spec = await loadOpenAPISpec();
// Get the response schema for a specific endpoint
const responseSchema = await getSchemaForEndpoint('/experiments', 'GET', 200);
// Get the request body schema
const requestSchema = await getRequestSchemaForEndpoint('/goals', 'POST');
// Compile a reusable validator
const validate = createValidator(responseSchema);
const isValid = validate(data);
// One-shot validation
const result = await validateAgainstSchema(data, schema, { coerceTypes: true });OpenAPI Specification
The package bundles the ABsmartly API OpenAPI 3.0 specification:
openapi/
openapi.yaml # Main spec (references paths/ and components/)
openapi.bundle.yaml # Fully dereferenced bundle (used at runtime)
openapi_with_examples.yaml
paths/ # Per-resource path definitions (44 files)
examples/ # Response examples per resource
components/ # Shared schema components
live-payloads/ # Sanitized payloads captured from the live APIThe bundled spec at openapi/openapi.bundle.yaml is what the validation layer loads at runtime. It has all $ref references resolved inline.
Code Generation
Two things are auto-generated from the OpenAPI spec and must be regenerated whenever the spec changes:
TypeScript Types
npm run generate:typesRuns openapi-typescript to produce src/generated/schema.d.ts from openapi/openapi.yaml. This file contains TypeScript interfaces for all API paths, operations, and component schemas. All factories are typed using these generated types.
MSW Handlers
npm run generate:handlersRuns scripts/generate-handlers.mjs to produce one handler file per API resource in src/handlers/generated/. The generator:
- Reads each path from the OpenAPI spec
- Extracts response examples (preferring
_200/_201suffixed examples) - Creates MSW handlers for each HTTP method (GET, POST, PUT, DELETE, etc.)
- Wraps POST/PUT/PATCH handlers with
withValidation()for request validation - Generates an
index.tsthat aggregates all resource handlers
Regenerate Everything
npm run generateThis runs both generate:types and generate:handlers. It also runs automatically before npm test (via pretest) and before npm publish (via prepublishOnly).
Testing
Unit Tests
npm test # Watch mode
npm run test:run # Single run
npm run test:coverage # With coverage reportUnit tests validate the mock server handlers, factories, and validation logic using MSW's in-memory server. No network access required.
Contract Tests
npm run test:contractValidates that the mock server responses conform to the OpenAPI schema. Compiles schemas with AJV and checks that generated responses pass validation.
Live API Validation
npm run validate:liveRuns 40+ test files against the real ABsmartly API, validating that actual API responses match the OpenAPI schema. Requires API_KEY and API_BASE_URL environment variables (or a .env.local file).
npm run validate:live:report # Generates a markdown report
npm run validate:comprehensive # Full validation suite with reportsMutation Tests
npm run validate:mutations # Interactive (asks for confirmation)
npm run validate:mutations:force # Non-interactiveFull CRUD lifecycle tests that create, read, update, and delete resources on the live API. Includes automatic cleanup of created resources via a ResourceTracker.
Schemathesis
npm run validate:schemathesisProperty-based API testing using Schemathesis. Generates random valid requests from the OpenAPI spec and checks all responses for conformance. Requires Python 3.11+ and the schemathesis pip package.
Environment Variables
| Variable | Description | Default |
|---|---|---|
| API_BASE_URL | Base URL for the ABsmartly API (required for live tests and scripts) | -- |
| ABSMARTLY_API_ENDPOINT | Alternative base URL variable | -- |
| API_KEY | API key for live API tests | -- |
| ABSMARTLY_API_KEY | Alternative API key variable | -- |
| ENABLE_LIVE_API_TESTS | Set to true to enable live API test suites | false |
Create a .env.local file for local development:
API_KEY=your_api_key_here
API_BASE_URL=https://your-instance.absmartly.com/v1CI/CD
Pushes to main automatically run tests and publish a new patch version to npm via GitHub Actions with npm trusted publishing (OIDC -- no tokens required).
Project Structure
src/
index.ts # Main export aggregator
server.ts # createServer(baseUrl) factory
mocks/
server.ts # Pre-configured MSW server instance
handlers.ts # Handler initialization from env vars
handlers/
index.ts # Handler exports and composition
errors.ts # Error simulation handlers
generated/ # Auto-generated (one file per resource)
factories/ # Faker-based data factories (18 resources)
validation/
schema-validator.ts # OpenAPI spec loading and AJV validation
request-validator.ts # Lenient request validation
response-validator.ts # Strict response validation
validated-handler.ts # MSW handler validation wrapper
strict-validator.ts # Strict validation + unknown property detection
payload-generator.ts # Generate valid payloads from schemas
generated/
schema.d.ts # Auto-generated TypeScript types
__tests__/ # Unit tests
contract-tests/ # Schema contract tests
live-validation/ # Live API validation tests
live-validation-mutations/# CRUD lifecycle mutation tests
openapi/
openapi.yaml # Main OpenAPI spec
openapi.bundle.yaml # Dereferenced bundle
paths/ # Per-resource path definitions
examples/ # Response examples
components/ # Shared schemas
live-payloads/ # Captured live API payloads
scripts/
generate-handlers.mjs # Handler generation from OpenAPI
generate-live-validation-tests-v2.mjs
validate-live-api.sh
run-mutation-tests.sh
sync-examples-from-live.mjsLicense
Proprietary - ABsmartly
