Files
proxy/nodejs/test
wmantly 6092468901 Add per-host reverse-proxy controls (rate limit, cache, headers, IP ACL)
Every proxied request flows through one shared OpenResty location whose
behavior is chosen at request time from the host's Redis hash. Add per-host
controls as new Host fields enforced in Lua rather than static nginx config
(which can't key off a per-request variable):

- Rate limiting: per-client-IP token bucket via resty.limit.req
  (ratelimit_enabled/rate/burst), backed by a new `ratelimit` shared dict.
- Response caching: opt-in per host via a global proxy_cache zone gated by
  $skip_cache (respcache_enabled). Off by default; upstream Cache-Control
  still honored.
- Custom/security headers: req_headers (upstream) + resp_headers (client) and
  hsts_enabled, applied in access/header_filter phases.
- IP allow/deny CIDR lists via resty.ipmatcher (deny wins; non-empty allow is
  default-deny).

New ops/nginx_conf/hostfeatures.lua holds the enforcement; proxy.conf's
access_by_lua string becomes a block that calls it, plus a header_filter block.
nodejs/utils/host_features.js is the pure, unit-tested normalize/validate layer
(header/CIDR parsing, range clamping, injection-safe values) applied in
routes/host.js and mirrored by the hosts.ejs edit form. install.sh gains the
ipmatcher rock, the cache dir, and the hostfeatures.lua symlink.

Per-host cache TTL is intentionally deferred (global default only) — see the
plan's limitations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:10:55 -04:00
..
2025-12-31 16:51:55 -05:00
2025-12-31 16:51:55 -05:00

Test Suite

This project uses Node.js built-in test runner (requires Node 18+). No external testing dependencies required.

Running Tests

# Run all tests
npm test

# Run only unit tests
npm run test:unit

# Run only integration tests
npm run test:integration

# Run tests in watch mode (auto-rerun on file changes)
npm run test:watch

Test Structure

test/
├── unit/              # Unit tests for isolated components
│   ├── callback_queue.test.js
│   ├── host_lookup.test.js
│   └── unix_socket.test.js
├── integration/       # Integration tests for complex interactions
│   └── dns_provider.test.js
└── helpers/           # Test utilities and contracts
    └── dns_provider_contract.js

What We Test

Unit Tests

callback_queue.test.js

  • Callback registration and invocation
  • Multiple callbacks with arguments
  • Error handling

host_lookup.test.js

  • Host lookup tree algorithm
  • Wildcard matching (single and double)
  • Exact match priority
  • Edge cases (no match, empty input, etc.)

unix_socket.test.js

  • Unix socket server creation
  • JSON message parsing
  • Partial data buffering
  • Multiple connections
  • Error handling

Integration Tests

dns_provider.test.js

  • DNS provider contract compliance
  • All existing providers (CloudFlare, DigitalOcean, PorkBun)
  • Method signatures
  • Key mapping
  • Type validation

Adding a New DNS Provider

When you add a new DNS provider, you MUST add tests to ensure it meets the contract:

  1. Create your provider class extending DnsApi in models/dns_provider/yourprovider.js

  2. Add a test block in test/integration/dns_provider.test.js:

describe('YourProvider Provider', () => {
    const YourProvider = require('../../models/dns_provider/yourprovider');

    test('should meet DNS provider contract', () => {
        const mockCredentials = {api_key: 'mock-key'};
        const instance = validateDnsProviderContract(YourProvider, mockCredentials);
        assert.ok(instance, 'YourProvider should be instantiated');
    });

    test('should have correct _keyMap structure', () => {
        // Test your specific credential requirements
        assert.ok(YourProvider._keyMap.api_key);
        assert.strictEqual(YourProvider._keyMap.api_key.type, 'string');
        assert.strictEqual(YourProvider._keyMap.api_key.isRequired, true);
    });

    test('should have valid method signatures', () => {
        const instance = new YourProvider({api_key: 'mock'});
        validateMethodSignatures(instance);
    });

    test('should validate key mapping', () => {
        const instance = new YourProvider({api_key: 'mock'});
        validateKeyMapping(instance);
    });

    test('should validate type checking', () => {
        const instance = new YourProvider({api_key: 'mock'});
        validateTypeChecking(instance);
    });
});
  1. Run tests to verify compliance:
npm run test:integration

DNS Provider Contract

All DNS providers must:

  1. Extend DnsApi base class
  2. Define static _keyMap with required credentials
  3. Define static display properties: displayName, displayIconHtml, displayIconUni
  4. Implement required methods:
    • listDomains() - Returns array of {domain, zoneId}
    • getRecords(domain, options) - Returns array of DNS records
    • createRecord(domain, options) - Creates a record
    • deleteRecords(domain, options) - Deletes matching records
  5. Define __apiKeyMap to translate between class keys and API keys
  6. Implement or inherit __typeCheck() for record type validation
  7. Throw appropriate errors from this.errors object

CI/CD Integration

Tests can be run in GitHub Actions, GitLab CI, or any CI/CD system:

# Example GitHub Actions workflow
- name: Run tests
  run: npm test

Philosophy

We test custom logic, not third-party code:

  • YES: Test our host lookup algorithm
  • YES: Test our socket buffering logic
  • YES: Test DNS provider contracts
  • NO: Don't test Express.js routing
  • NO: Don't test the Redis ORM
  • NO: Don't test external DNS APIs (use mocks)

Notes

  • Tests use Node's built-in node:test and node:assert modules
  • No external testing framework needed
  • Tests are fast and run in parallel by default
  • Mock external services (Redis, DNS APIs) to avoid network calls
  • Focus on testing business logic, not infrastructure