diff --git a/configuration.ts b/configuration.ts index feb2627d..c1b66ae8 100644 --- a/configuration.ts +++ b/configuration.ts @@ -158,7 +158,7 @@ export class Configuration { } this.baseOptions = baseOptions; - this.retryParams = params.retryParams; + this.retryParams = Object.assign(GetDefaultRetryParams(), params.retryParams); this.telemetry = new TelemetryConfiguration(params?.telemetry?.metrics); } diff --git a/tests/client.test.ts b/tests/client.test.ts index fbae2b77..4fe36b17 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -11,14 +11,12 @@ import { ListUsersResponse, ConsistencyPreference, ErrorCode, - BatchCheckRequest, ClientWriteRequestOnDuplicateWrites, ClientWriteRequestOnMissingDeletes, } from "../index"; import { baseConfig, defaultConfiguration, getNocks } from "./helpers"; const nocks = getNocks(nock); -nock.disableNetConnect(); describe("OpenFGA Client", () => { @@ -1875,7 +1873,8 @@ describe("OpenFGA Client", () => { const scope0 = nocks.check(defaultConfiguration.storeId!, tuples[0], defaultConfiguration.getBasePath(), { allowed: true }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); const scope1 = nocks.check(defaultConfiguration.storeId!, tuples[1], defaultConfiguration.getBasePath(), { allowed: false }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); const scope2 = nocks.check(defaultConfiguration.storeId!, tuples[2], defaultConfiguration.getBasePath(), { allowed: true }).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); - const scope3 = nocks.check(defaultConfiguration.storeId!, tuples[3], defaultConfiguration.getBasePath(), "" as any, 500).matchHeader("X-OpenFGA-Client-Method", "ListRelations"); + // Mock all default retries+1 to exhaust them all and trigger actual failure. + const scope3 = Array.from({ length: 4 }, () => nocks.check(defaultConfiguration.storeId!, tuples[3], defaultConfiguration.getBasePath(), "" as any, 500).matchHeader("X-OpenFGA-Client-Method", "ListRelations")); const scope4 = nocks.check(defaultConfiguration.storeId!, tuples[4], defaultConfiguration.getBasePath(), { "code": "validation_error", "message": "relation 'workspace#can_read' not found" @@ -1890,12 +1889,12 @@ describe("OpenFGA Client", () => { expect(scope0.isDone()).toBe(false); expect(scope1.isDone()).toBe(false); expect(scope2.isDone()).toBe(false); - expect(scope3.isDone()).toBe(false); + expect(scope3.every(scope => scope.isDone())).toBe(false); expect(scope4.isDone()).toBe(false); expect(scope5.isDone()).toBe(false); try { - const response = await fgaClient.listRelations({ + await fgaClient.listRelations({ user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", object: "workspace:1", relations: ["admin", "guest", "reader", "viewer"], @@ -1904,7 +1903,7 @@ describe("OpenFGA Client", () => { expect(scope0.isDone()).toBe(true); expect(scope1.isDone()).toBe(true); expect(scope2.isDone()).toBe(true); - expect(scope3.isDone()).toBe(true); + expect(scope3.every(scope => scope.isDone())).toBe(true); expect(scope4.isDone()).toBe(false); expect(scope5.isDone()).toBe(false); expect(err).toBeInstanceOf(FgaApiError); diff --git a/tests/index.test.ts b/tests/index.test.ts index e3c661cc..0830e125 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -425,12 +425,56 @@ describe("OpenFGA SDK", function () { }); }); + describe("429 with default retry config is successful", () => { + const tupleKey = { + user: "user:xyz", + relation: "viewer", + object: "foobar:x", + }; + let failedRequestNock: nock.Scope; + + beforeEach(async () => { + fgaApi = new OpenFgaApi({ ...baseConfig }); + + nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token"); + + failedRequestNock = nock(basePath) + .post( + `/stores/${storeId}/check`, + { + tuple_key: tupleKey, + authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1" + }, + expect.objectContaining({ Authorization: "Bearer test-token" }) + ) + .times(1) + .reply(429, { + code: "rate_limit_exceeded", + message: "nock error", + }); + + nocks.check(baseConfig.storeId!, tupleKey); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + it("should return allowed", async () => { + const result = await fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey, authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"}, {}); + + expect(failedRequestNock.isDone()).toBe(true); + expect(result.allowed).toBe(true); + }); + }); + describe("429 with retry in config and retry is successful", () => { const tupleKey = { user: "user:xyz", relation: "viewer", object: "foobar:x", }; + let failedRequestNock: nock.Scope; beforeEach(async () => { const updateBaseConfig = { @@ -441,11 +485,12 @@ describe("OpenFGA SDK", function () { nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token"); - nock(basePath) + failedRequestNock = nock(basePath) .post( `/stores/${storeId}/check`, { tuple_key: tupleKey, + authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1" }, expect.objectContaining({ Authorization: "Bearer test-token" }) ) @@ -465,6 +510,7 @@ describe("OpenFGA SDK", function () { it("should return allowed", async () => { const result = await fgaApi.check(baseConfig.storeId!, { tuple_key: tupleKey, authorization_model_id: "01GXSA8YR785C4FYS3C0RTG7B1"}, {}); + expect(failedRequestNock.isDone()).toBe(true); expect(result.allowed).toBe(true); }); }); @@ -475,11 +521,12 @@ describe("OpenFGA SDK", function () { relation: "viewer", object: "foobar:x", }; + let failedRequestNock: nock.Scope; beforeEach(async () => { nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token"); - nock(basePath) + failedRequestNock = nock(basePath) .post( `/stores/${storeId}/check`, { @@ -507,6 +554,7 @@ describe("OpenFGA SDK", function () { { retryParams: GetDefaultRetryParams(2, 10) } ); + expect(failedRequestNock.isDone()).toBe(true); expect(result.allowed).toBe(true); }); }); diff --git a/tests/setup.ts b/tests/setup.ts index c756aa32..de84c749 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,5 +1,9 @@ import * as nock from "nock"; +beforeEach(() => { + nock.disableNetConnect(); +}); + afterAll(() => { nock.restore(); }); \ No newline at end of file