feat(feature-control): add standalone BFF with bootstrap, exposure telemetry and tests

This commit is contained in:
Daniel Arantes Loverde
2026-04-16 14:46:38 -03:00
parent 2bc70c7d2a
commit f8d2fb8eb7
5 changed files with 741 additions and 0 deletions

View File

@@ -0,0 +1,275 @@
import test from "node:test";
import assert from "node:assert/strict";
import http from "node:http";
import { spawn } from "node:child_process";
function listen(server) {
return new Promise((resolve) => {
server.listen(0, "127.0.0.1", () => {
const address = server.address();
resolve(address.port);
});
});
}
function closeServer(server) {
return new Promise((resolve) => server.close(() => resolve()));
}
async function waitForHealth(url, timeoutMs = 5000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
try {
const response = await fetch(`${url}/health`);
if (response.ok) return;
} catch {}
await new Promise((resolve) => setTimeout(resolve, 100));
}
throw new Error("BFF health timeout");
}
async function startBff(env) {
const child = spawn("node", ["server.mjs"], {
cwd: new URL("..", import.meta.url),
env: { ...process.env, ...env },
stdio: ["ignore", "pipe", "pipe"]
});
const logs = [];
child.stdout.on("data", (chunk) => logs.push(chunk.toString("utf-8")));
child.stderr.on("data", (chunk) => logs.push(chunk.toString("utf-8")));
const port = Number(env.PORT);
const baseUrl = `http://127.0.0.1:${port}`;
await waitForHealth(baseUrl);
return {
baseUrl,
logs,
stop: async () => {
child.kill("SIGTERM");
await new Promise((resolve) => child.on("exit", () => resolve()));
}
};
}
function createUpstreamStub(options = {}) {
const state = {
evaluateMode: options.evaluateMode || "success",
evaluatePayload: null,
exposurePayload: null
};
const server = http.createServer(async (req, res) => {
if (req.method === "POST" && req.url === "/api/feature-control/evaluate") {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
state.evaluatePayload = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
if (state.evaluateMode === "429") {
res.writeHead(429, { "Content-Type": "application/json" });
res.end(JSON.stringify({ code: "FEATURE_CONTROL_RATE_LIMIT", message: "rate" }));
return;
}
if (state.evaluateMode === "500") {
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ code: "FEATURE_CONTROL_ERROR", message: "boom" }));
return;
}
if (state.evaluateMode === "timeout") {
await new Promise((resolve) => setTimeout(resolve, 600));
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
error: false,
code: "FEATURE_CONTROL_EVALUATED",
result: {
evaluatedAt: "2026-04-16T12:00:00.000Z",
configVersion: 7,
flags: {
"fc.checkout_v2": { enabled: true, variant: "on", payload: null, reason: "rollout" },
"fc.search_ranking_v3": { enabled: false, variant: "off", payload: null, reason: "default" }
}
}
})
);
return;
}
if (req.method === "POST" && req.url === "/api/feature-control/telemetry/exposure") {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
state.exposurePayload = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: false, code: "FEATURE_CONTROL_EXPOSURE_TRACKED", result: { accepted: true } }));
return;
}
res.writeHead(404).end();
});
return { server, state };
}
function bootstrapBody() {
return {
environment: "production",
keys: ["fc.checkout_v2", "fc.search_ranking_v3"],
context: {
subjectType: "customer",
subjectId: "cust_123",
storeId: "store_001",
platform: "ios",
appVersion: "2.3.1",
attributes: { city: "Campinas" }
}
};
}
test("bootstrap sucesso retorna flags simplificadas e raw", async () => {
const upstream = createUpstreamStub({ evaluateMode: "success" });
const upstreamPort = await listen(upstream.server);
const bffPort = upstreamPort + 1000;
const bff = await startBff({
PORT: String(bffPort),
ATOMENTA_ORIGIN: `http://127.0.0.1:${upstreamPort}`,
FEATURE_CONTROL_MODULE_TOKEN: "token",
FEATURE_CONTROL_TIMEOUT_MS: "300"
});
try {
const response = await fetch(`${bff.baseUrl}/feature-control/bootstrap`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(bootstrapBody())
});
assert.equal(response.status, 200);
const json = await response.json();
assert.equal(json.ok, true);
assert.equal(json.configVersion, 7);
assert.equal(json.flags.checkoutV2, true);
assert.equal(json.flags.searchRankingV3, "off");
assert.ok(json.raw["fc.checkout_v2"]);
assert.equal(upstream.state.evaluatePayload.context.subjectId, "cust_123");
} finally {
await bff.stop();
await closeServer(upstream.server);
}
});
test("bootstrap com 429 devolve fallback sem quebrar cliente", async () => {
const upstream = createUpstreamStub({ evaluateMode: "429" });
const upstreamPort = await listen(upstream.server);
const bffPort = upstreamPort + 1000;
const bff = await startBff({
PORT: String(bffPort),
ATOMENTA_ORIGIN: `http://127.0.0.1:${upstreamPort}`,
FEATURE_CONTROL_MODULE_TOKEN: "token",
FEATURE_CONTROL_DEFAULTS_JSON: JSON.stringify({
"fc.checkout_v2": { enabled: false, variant: "off" }
})
});
try {
const response = await fetch(`${bff.baseUrl}/feature-control/bootstrap`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(bootstrapBody())
});
assert.equal(response.status, 200);
const json = await response.json();
assert.equal(json.source, "fallback");
assert.equal(json.upstreamError.status, 429);
assert.equal(json.flags.checkoutV2, "off");
} finally {
await bff.stop();
await closeServer(upstream.server);
}
});
test("bootstrap com timeout devolve fallback", async () => {
const upstream = createUpstreamStub({ evaluateMode: "timeout" });
const upstreamPort = await listen(upstream.server);
const bffPort = upstreamPort + 1000;
const bff = await startBff({
PORT: String(bffPort),
ATOMENTA_ORIGIN: `http://127.0.0.1:${upstreamPort}`,
FEATURE_CONTROL_MODULE_TOKEN: "token",
FEATURE_CONTROL_TIMEOUT_MS: "100"
});
try {
const response = await fetch(`${bff.baseUrl}/feature-control/bootstrap`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(bootstrapBody())
});
assert.equal(response.status, 200);
const json = await response.json();
assert.equal(json.source, "fallback");
assert.equal(json.upstreamError.code, "FEATURE_CONTROL_UPSTREAM_TIMEOUT");
} finally {
await bff.stop();
await closeServer(upstream.server);
}
});
test("telemetria de exposure encaminha lote para upstream", async () => {
const upstream = createUpstreamStub({ evaluateMode: "success" });
const upstreamPort = await listen(upstream.server);
const bffPort = upstreamPort + 1000;
const bff = await startBff({
PORT: String(bffPort),
ATOMENTA_ORIGIN: `http://127.0.0.1:${upstreamPort}`,
FEATURE_CONTROL_MODULE_TOKEN: "token"
});
try {
const response = await fetch(`${bff.baseUrl}/feature-control/telemetry/exposure`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
events: [
{ featureKey: "fc.checkout_v2", variant: "on", subjectType: "customer", storeId: "store_1" }
]
})
});
assert.equal(response.status, 200);
const json = await response.json();
assert.equal(json.ok, true);
assert.equal(upstream.state.exposurePayload.events[0].featureKey, "fc.checkout_v2");
} finally {
await bff.stop();
await closeServer(upstream.server);
}
});
test("telemetria rejeita payload sem eventos", async () => {
const upstream = createUpstreamStub({ evaluateMode: "success" });
const upstreamPort = await listen(upstream.server);
const bffPort = upstreamPort + 1000;
const bff = await startBff({
PORT: String(bffPort),
ATOMENTA_ORIGIN: `http://127.0.0.1:${upstreamPort}`,
FEATURE_CONTROL_MODULE_TOKEN: "token"
});
try {
const response = await fetch(`${bff.baseUrl}/feature-control/telemetry/exposure`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ events: [] })
});
assert.equal(response.status, 400);
const json = await response.json();
assert.equal(json.ok, false);
assert.equal(json.code, "FEATURE_CONTROL_EVENTS_REQUIRED");
} finally {
await bff.stop();
await closeServer(upstream.server);
}
});