From 5bf21bc856b65c25b228198972ee4381ee6fd3f0 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 20 Jul 2026 10:32:31 -0400 Subject: [PATCH] test: cover DNS entrypoint discovery --- test/discovery.test.ts | 45 +++++++++++++- .../alternator-client.test.ts | 59 ++++++++++++++++++- 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/test/discovery.test.ts b/test/discovery.test.ts index 1850f6d..8eb1f5e 100644 --- a/test/discovery.test.ts +++ b/test/discovery.test.ts @@ -325,6 +325,47 @@ describe("Alternator discovery", () => { } }); + it("resolves DNS entrypoint and keeps DNS node records", async () => { + let hostHeader = ""; + const server = createServer((request, response) => { + expect(request.url).toBe("/localnodes"); + hostHeader = request.headers.host ?? ""; + response.setHeader("content-type", "application/json"); + response.end(JSON.stringify(["localhost", "node-a.internal"])); + }); + const address = await listen(server, "localhost"); + const client = new AlternatorDynamoDBClient({ + seeds: ["localhost"], + port: address.port, + discovery: { + background: false, + timeoutMs: 500, + }, + }); + + try { + await expect(client.alternator.refreshNodes()).resolves.toEqual([ + { + host: "localhost", + scheme: "http", + port: address.port, + url: `http://localhost:${address.port}`, + }, + { + host: "node-a.internal", + scheme: "http", + port: address.port, + url: `http://node-a.internal:${address.port}`, + }, + ]); + expect(hostHeader).toBe(`localhost:${address.port}`); + } finally { + client.destroy(); + server.closeAllConnections?.(); + await close(server); + } + }); + it("bounds draining non-terminating non-2xx discovery bodies", async () => { let requests = 0; const server = createServer((request, response) => { @@ -419,10 +460,10 @@ describe("Alternator discovery", () => { }); }); -function listen(server: Server): Promise { +function listen(server: Server, host = "127.0.0.1"): Promise { return new Promise((resolve, reject) => { server.once("error", reject); - server.listen(0, "127.0.0.1", () => { + server.listen(0, host, () => { server.off("error", reject); resolve(server.address() as AddressInfo); }); diff --git a/test/integration-test/alternator-client.test.ts b/test/integration-test/alternator-client.test.ts index 4c57f4c..de7d626 100644 --- a/test/integration-test/alternator-client.test.ts +++ b/test/integration-test/alternator-client.test.ts @@ -1,6 +1,8 @@ import { GetItemCommand, ListTablesCommand, PutItemCommand } from "@aws-sdk/client-dynamodb"; +import { createServer, type Server } from "node:http"; +import type { AddressInfo } from "node:net"; import { describe, expect, it } from "vitest"; -import { routing } from "../../src/index.js"; +import { AlternatorDynamoDBClient, routing } from "../../src/index.js"; import { describeIntegration, integrationConfig, integrationEndpoints } from "./config.js"; import { buildClient, @@ -351,6 +353,39 @@ describeIntegration.each(integrationEndpoints())( }, ); +describeIntegration.each(integrationEndpoints().filter((endpoint) => endpoint.scheme === "http"))( + "Alternator DNS entrypoint integration ($name)", + (endpoint) => { + it("discovers live cluster nodes from a DNS entrypoint", async () => { + const localnodes = await fetch(`http://${endpoint.host}:${endpoint.port}/localnodes`); + expect(localnodes.ok).toBe(true); + const body = await localnodes.text(); + + const server = createServer((request, response) => { + expect(request.url).toBe("/localnodes"); + response.setHeader("content-type", "application/json"); + response.end(body); + }); + const address = await listen(server, "localhost"); + const client = new AlternatorDynamoDBClient({ + seeds: ["localhost"], + scheme: "http", + port: address.port, + credentials: integrationConfig.credentials, + discovery: { background: false }, + }); + + try { + const nodes = await client.alternator.refreshNodes(); + expect(nodes.length).toBeGreaterThan(0); + } finally { + client.destroy(); + await close(server); + } + }); + }, +); + describe("integration test opt-in", () => { it("is disabled unless INTEGRATION_TESTS is truthy", () => { expect(typeof integrationConfig.enabled).toBe("boolean"); @@ -367,3 +402,25 @@ function signedHeaderNames(authorization: string | undefined): string[] { const match = authorization?.match(/(?:^|,\s*)SignedHeaders=([^,\s]+)/); return match?.[1]?.split(";").filter(Boolean) ?? []; } + +function listen(server: Server, host: string): Promise { + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, host, () => { + server.off("error", reject); + resolve(server.address() as AddressInfo); + }); + }); +} + +function close(server: Server): Promise { + return new Promise((resolve, reject) => { + server.close((error) => { + if (error) { + reject(error); + return; + } + resolve(); + }); + }); +}