npm.io
1.0.9 • Published 20h ago

@absmartly/api-mocks

Licence
UNLICENSED
Version
1.0.9
Deps
5
Size
3.8 MB
Vulns
0
Weekly
0

@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-mocks

Peer dependency: This package requires msw v2+:

npm install --save-dev msw

Quick 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:

  1. API_BASE_URL environment variable
  2. ABSMARTLY_API_ENDPOINT environment variable
  3. https://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 id field 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 -> "test@example.com", 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 API

The 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:types

Runs 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:handlers

Runs scripts/generate-handlers.mjs to produce one handler file per API resource in src/handlers/generated/. The generator:

  1. Reads each path from the OpenAPI spec
  2. Extracts response examples (preferring _200/_201 suffixed examples)
  3. Creates MSW handlers for each HTTP method (GET, POST, PUT, DELETE, etc.)
  4. Wraps POST/PUT/PATCH handlers with withValidation() for request validation
  5. Generates an index.ts that aggregates all resource handlers
Regenerate Everything
npm run generate

This 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 report

Unit 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:contract

Validates 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:live

Runs 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 reports
Mutation Tests
npm run validate:mutations       # Interactive (asks for confirmation)
npm run validate:mutations:force # Non-interactive

Full 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:schemathesis

Property-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/v1

CI/CD

Pushes to main run tests, then publish to npm via GitHub Actions with npm trusted publishing (OIDC -- no tokens required) only if package.json's version isn't already published. The version is never bumped automatically -- bump it yourself (e.g. npm version patch) as part of the PR that should trigger a release; a push that doesn't change the version just runs tests.

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.mjs

License

Proprietary - ABsmartly

Keywords