From 66de1e303178c789c183e4fe6f5ea1e0ffed37b0 Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Fri, 17 Apr 2026 14:57:53 -0300 Subject: [PATCH] Apps --- feature-control-bff/README.md | 23 ++++++++++++++----- feature-control-bff/tests/bff.test.mjs | 22 ++++++++++-------- .../Services/FeatureControlService.swift | 4 ++-- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/feature-control-bff/README.md b/feature-control-bff/README.md index ac6f863..14aa022 100644 --- a/feature-control-bff/README.md +++ b/feature-control-bff/README.md @@ -8,6 +8,11 @@ BFF mínimo para expor flags ao app sem expor `Atomenta-Token` no cliente. - `POST /feature-control/telemetry/exposure` - `GET /health` +## Notificações + +No momento este BFF cobre apenas Feature Control (`bootstrap` + `exposure`). +Para notificações segmentadas por audiência (`todos`, `feature:fc.ios`), precisamos adicionar o contrato de endpoint de notificações do Atomenta para integrar no app. + ## Variáveis de ambiente - `PORT` (default: `8787`) @@ -22,8 +27,11 @@ Exemplo de `FEATURE_CONTROL_DEFAULTS_JSON`: ```json { - "fc.checkout_v2": { "enabled": false, "variant": "off" }, - "fc.search_ranking_v3": { "enabled": true, "variant": "on", "payload": { "model": "v3" } } + "fc.promo": { "enabled": true, "variant": "on" }, + "fc.promo-codes": { "enabled": true, "variant": "on" }, + "fc.android": { "enabled": true, "variant": "on" }, + "fc.city-aguai": { "enabled": true, "variant": "on" }, + "fc.ios": { "enabled": true, "variant": "on" } } ``` @@ -32,7 +40,7 @@ Exemplo de `FEATURE_CONTROL_DEFAULTS_JSON`: ```json { "environment": "production", - "keys": ["fc.checkout_v2", "fc.search_ranking_v3"], + "keys": ["fc.promo", "fc.promo-codes", "fc.android", "fc.city-aguai", "fc.ios"], "context": { "subjectType": "customer", "subjectId": "cust_123", @@ -58,11 +66,14 @@ Se o app enviar `Authorization: Bearer `, o BFF tenta extrair `subjectId` d "configVersion": 7, "evaluatedAt": "2026-04-16T12:00:00.000Z", "flags": { - "checkoutV2": true, - "searchRankingV3": "off" + "promo": true, + "promoCodes": true, + "android": true, + "cityAguai": true, + "ios": true }, "raw": { - "fc.checkout_v2": { + "fc.promo": { "enabled": true, "variant": "on", "payload": null, diff --git a/feature-control-bff/tests/bff.test.mjs b/feature-control-bff/tests/bff.test.mjs index 98ba28d..fa41cc3 100644 --- a/feature-control-bff/tests/bff.test.mjs +++ b/feature-control-bff/tests/bff.test.mjs @@ -91,8 +91,9 @@ function createUpstreamStub(options = {}) { 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" } + "fc.promo": { enabled: true, variant: "on", payload: null, reason: "rollout" }, + "fc.promo-codes": { enabled: true, variant: "on", payload: null, reason: "rollout" }, + "fc.ios": { enabled: true, variant: "on", payload: null, reason: "audience" } } } }) @@ -118,7 +119,7 @@ function createUpstreamStub(options = {}) { function bootstrapBody() { return { environment: "production", - keys: ["fc.checkout_v2", "fc.search_ranking_v3"], + keys: ["fc.promo", "fc.promo-codes", "fc.ios"], context: { subjectType: "customer", subjectId: "cust_123", @@ -151,9 +152,10 @@ test("bootstrap sucesso retorna flags simplificadas e raw", async () => { 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(json.flags.promo, true); + assert.equal(json.flags.promoCodes, true); + assert.equal(json.flags.ios, true); + assert.ok(json.raw["fc.promo"]); assert.equal(upstream.state.evaluatePayload.context.subjectId, "cust_123"); } finally { await bff.stop(); @@ -170,7 +172,7 @@ test("bootstrap com 429 devolve fallback sem quebrar cliente", async () => { 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" } + "fc.promo": { enabled: false, variant: "off" } }) }); @@ -184,7 +186,7 @@ test("bootstrap com 429 devolve fallback sem quebrar cliente", async () => { const json = await response.json(); assert.equal(json.source, "fallback"); assert.equal(json.upstreamError.status, 429); - assert.equal(json.flags.checkoutV2, "off"); + assert.equal(json.flags.promo, "off"); } finally { await bff.stop(); await closeServer(upstream.server); @@ -234,14 +236,14 @@ test("telemetria de exposure encaminha lote para upstream", async () => { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ events: [ - { featureKey: "fc.checkout_v2", variant: "on", subjectType: "customer", storeId: "store_1" } + { featureKey: "fc.promo", 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"); + assert.equal(upstream.state.exposurePayload.events[0].featureKey, "fc.promo"); } finally { await bff.stop(); await closeServer(upstream.server); diff --git a/pedi-foods/Sources/PediFoods/Services/FeatureControlService.swift b/pedi-foods/Sources/PediFoods/Services/FeatureControlService.swift index 312a157..477ec9b 100644 --- a/pedi-foods/Sources/PediFoods/Services/FeatureControlService.swift +++ b/pedi-foods/Sources/PediFoods/Services/FeatureControlService.swift @@ -258,12 +258,12 @@ final class FeatureControlService { } private func featureKeys() -> [String] { - let raw = ProcessInfo.processInfo.environment["FEATURE_CONTROL_KEYS"] ?? "fc.checkout_v2,fc.search_ranking_v3" + let raw = ProcessInfo.processInfo.environment["FEATURE_CONTROL_KEYS"] ?? "fc.promo,fc.promo-codes,fc.android,fc.city-aguai,fc.ios" let items = raw .split(separator: ",") .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { $0.isEmpty == false } - return items.isEmpty ? ["fc.checkout_v2"] : items + return items.isEmpty ? ["fc.ios"] : items } private func platformName() -> String {