smartproxy/test/test.acme-http01-challenge.ts
2025-05-29 10:13:41 +00:00

162 lines
4.8 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { SmartProxy, SocketHandlers } from '../ts/index.js';
import * as net from 'net';
// Test that HTTP-01 challenges are properly processed when the initial data arrives
tap.test('should correctly handle HTTP-01 challenge requests with initial data chunk', async (tapTest) => {
// Prepare test data
const challengeToken = 'test-acme-http01-challenge-token';
const challengeResponse = 'mock-response-for-challenge';
const challengePath = `/.well-known/acme-challenge/${challengeToken}`;
// Create a socket handler that responds to ACME challenges using httpServer
const acmeHandler = SocketHandlers.httpServer((req, res) => {
// Log request details for debugging
console.log(`Received request: ${req.method} ${req.url}`);
// Check if this is an ACME challenge request
if (req.url?.startsWith('/.well-known/acme-challenge/')) {
const token = req.url.substring('/.well-known/acme-challenge/'.length);
// If the token matches our test token, return the response
if (token === challengeToken) {
res.header('Content-Type', 'text/plain');
res.send(challengeResponse);
return;
}
}
// For any other requests, return 404
res.status(404);
res.header('Content-Type', 'text/plain');
res.send('Not found');
});
// Create a proxy with the ACME challenge route
const proxy = new SmartProxy({
routes: [{
name: 'acme-challenge-route',
match: {
ports: 8080,
path: '/.well-known/acme-challenge/*'
},
action: {
type: 'socket-handler',
socketHandler: acmeHandler
}
}]
});
await proxy.start();
// Create a client to test the HTTP-01 challenge
const testClient = new net.Socket();
let responseData = '';
// Set up client handlers
testClient.on('data', (data) => {
responseData += data.toString();
});
// Connect to the proxy and send the HTTP-01 challenge request
await new Promise<void>((resolve, reject) => {
testClient.connect(8080, 'localhost', () => {
// Send HTTP request for the challenge token
testClient.write(
`GET ${challengePath} HTTP/1.1\r\n` +
'Host: test.example.com\r\n' +
'User-Agent: ACME Challenge Test\r\n' +
'Accept: */*\r\n' +
'\r\n'
);
resolve();
});
testClient.on('error', reject);
});
// Wait for the response
await new Promise(resolve => setTimeout(resolve, 100));
// Verify that we received a valid HTTP response with the challenge token
expect(responseData).toContain('HTTP/1.1 200');
expect(responseData).toContain('Content-Type: text/plain');
expect(responseData).toContain(challengeResponse);
// Cleanup
testClient.destroy();
await proxy.stop();
});
// Test that non-existent challenge tokens return 404
tap.test('should return 404 for non-existent challenge tokens', async (tapTest) => {
// Create a socket handler that behaves like a real ACME handler
const acmeHandler = SocketHandlers.httpServer((req, res) => {
if (req.url?.startsWith('/.well-known/acme-challenge/')) {
const token = req.url.substring('/.well-known/acme-challenge/'.length);
// In this test, we only recognize one specific token
if (token === 'valid-token') {
res.header('Content-Type', 'text/plain');
res.send('valid-response');
return;
}
}
// For all other paths or unrecognized tokens, return 404
res.status(404);
res.header('Content-Type', 'text/plain');
res.send('Not found');
});
// Create a proxy with the ACME challenge route
const proxy = new SmartProxy({
routes: [{
name: 'acme-challenge-route',
match: {
ports: 8081,
path: '/.well-known/acme-challenge/*'
},
action: {
type: 'socket-handler',
socketHandler: acmeHandler
}
}]
});
await proxy.start();
// Create a client to test the invalid challenge request
const testClient = new net.Socket();
let responseData = '';
testClient.on('data', (data) => {
responseData += data.toString();
});
// Connect and send a request for a non-existent token
await new Promise<void>((resolve, reject) => {
testClient.connect(8081, 'localhost', () => {
testClient.write(
'GET /.well-known/acme-challenge/invalid-token HTTP/1.1\r\n' +
'Host: test.example.com\r\n' +
'\r\n'
);
resolve();
});
testClient.on('error', reject);
});
// Wait for the response
await new Promise(resolve => setTimeout(resolve, 100));
// Verify we got a 404 Not Found
expect(responseData).toContain('HTTP/1.1 404');
expect(responseData).toContain('Not found');
// Cleanup
testClient.destroy();
await proxy.stop();
});
tap.start();