Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/apiv2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import * as FormData from "form-data";
import * as auth from "./auth";
import nock from "./test/helpers/nock";
const proxySetup = require("proxy");

Check warning on line 7 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Require statement not part of import statement

Check warning on line 7 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value

import { Client, CLI_OAUTH_PROJECT_NUMBER } from "./apiv2";
import { FirebaseError } from "./error";
Expand All @@ -13,8 +13,8 @@
describe("apiv2", () => {
let authStub: sinon.SinonStub | undefined;
before(() => {
if (typeof (auth.getAccessToken as any).restore !== "function") {

Check warning on line 16 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unexpected any. Specify a different type

Check warning on line 16 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .restore on an `any` value
authStub = sinon.stub(auth, "getAccessToken").resolves({ access_token: "owner" } as any);

Check warning on line 17 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unexpected any. Specify a different type

Check warning on line 17 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe argument of type `any` assigned to a parameter of type `Tokens | undefined`
}
});
after(() => {
Expand Down Expand Up @@ -219,6 +219,55 @@
expect(nock.isDone()).to.be.true;
});

it("should not retry when retryOnPrematureClose is false", async () => {
nock("https://example.com").patch("/path/to/foo").once().replyWithError({
message:
"Invalid response body while trying to fetch https://example.com/path/to/foo: Premature close",
code: "ERR_STREAM_PREMATURE_CLOSE",
});
// If a retry fires, this second interceptor would be consumed
const retryScope = nock("https://example.com")
.patch("/path/to/foo")
.once()
.reply(200, { ok: true });

const c = new Client({ urlPrefix: "https://example.com" });
await expect(
c.request({
method: "PATCH",
path: "/path/to/foo",
body: { foo: "bar" },
retries: 1,
retryMinTimeout: 10,
retryMaxTimeout: 15,
retryOnPrematureClose: false,
}),
).to.eventually.be.rejectedWith(FirebaseError);
expect(retryScope.isDone()).to.be.false; // proves no retry occurred
nock.cleanAll();
});

it("should retry a request by default after a premature close error", async () => {
nock("https://example.com").post("/path/to/foo").once().replyWithError({
message:
"Invalid response body while trying to fetch https://example.com/path/to/foo: Premature close",
code: "ERR_STREAM_PREMATURE_CLOSE",
});
nock("https://example.com").post("/path/to/foo").once().reply(200, { ok: true });

const c = new Client({ urlPrefix: "https://example.com" });
const r = await c.request({
method: "POST",
path: "/path/to/foo",
body: { foo: "bar" },
retries: 1,
retryMinTimeout: 10,
retryMaxTimeout: 15,
});
expect(r.status).to.equal(200);
expect(nock.isDone()).to.be.true;
});

it("should not allow resolving on http error when streaming", async () => {
const c = new Client({ urlPrefix: "https://example.com" });
const r = c.request<unknown, NodeJS.ReadableStream>({
Expand Down Expand Up @@ -455,7 +504,7 @@
nock("https://example.com")
.get("/path/to/foo")
.reply(function (this: nock.ReplyFnContext): nock.ReplyFnResult {
expect(this.req.headers["x-goog-user-project"]).is.undefined;

Check warning on line 507 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe member access .req on an `any` value
return [200, { success: true }];
});
const prev = process.env["GOOGLE_CLOUD_QUOTA_PROJECT"];
Expand Down Expand Up @@ -598,7 +647,7 @@
let targetServer: Server;
let oldProxy: string | undefined;
before(async () => {
proxyServer = proxySetup(createServer());

Check warning on line 650 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe call of an `any` typed value

Check warning on line 650 in src/apiv2.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
targetServer = createServer((req, res) => {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ proxied: true }));
Expand Down
5 changes: 4 additions & 1 deletion src/apiv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

// Using import would require resolveJsonModule, which seems to break the
// build/output format.
const pkg = require("../package.json");

Check warning on line 19 in src/apiv2.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
const CLI_VERSION: string = pkg.version;

export const standardHeaders: () => Record<string, string> = () => {
Expand Down Expand Up @@ -94,6 +94,8 @@
retryMinTimeout?: number;
/** Maximum timeout between retries. Defaults to 5s. */
retryMaxTimeout?: number;
/** Set to false to disable the premature-close keep-alive retry for this request. Defaults to true. */
retryOnPrematureClose?: boolean;
}

export type ClientRequestOptions<T> = RequestOptions<T> & ClientVerbOptions;
Expand Down Expand Up @@ -581,7 +583,8 @@
!disabledKeepAlive &&
bodyReplayable &&
!proxyURIFromEnv() &&
isPrematureCloseError(err)
isPrematureCloseError(err) &&
options.retryOnPrematureClose !== false
) {
disabledKeepAlive = true;
fetchOptions.agent = noKeepAliveAgent;
Expand Down
4 changes: 3 additions & 1 deletion src/gcp/cloudfunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export async function createFunction(
const res = await client.post<Omit<CloudFunction, OutputOnlyFields>, CloudFunction>(
endpoint,
cloudFunction,
{ retryOnPrematureClose: false },
);
return {
name: res.body.name,
Expand Down Expand Up @@ -412,6 +413,7 @@ export async function updateFunction(
queryParams: {
updateMask: fieldMasks.join(","),
},
retryOnPrematureClose: false,
},
);
return {
Expand All @@ -431,7 +433,7 @@ export async function updateFunction(
export async function deleteFunction(name: string): Promise<Operation> {
const endpoint = `/${name}`;
try {
const res = await client.delete<Operation>(endpoint);
const res = await client.delete<Operation>(endpoint, { retryOnPrematureClose: false });
return {
done: false,
name: res.body.name,
Expand Down
6 changes: 3 additions & 3 deletions src/gcp/cloudfunctionsv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export async function createFunction(cloudFunction: InputCloudFunction): Promise
const res = await client.post<typeof cloudFunction, Operation>(
components.join("/"),
cloudFunction,
{ queryParams: { functionId } },
{ queryParams: { functionId }, retryOnPrematureClose: false },
);
return res.body;
} catch (err: any) {
Expand Down Expand Up @@ -415,7 +415,7 @@ export async function updateFunction(cloudFunction: InputCloudFunction): Promise
const res = await client.patch<typeof cloudFunction, Operation>(
cloudFunction.name,
cloudFunction,
{ queryParams },
{ queryParams, retryOnPrematureClose: false },
);
return res.body;
} catch (err: any) {
Expand All @@ -429,7 +429,7 @@ export async function updateFunction(cloudFunction: InputCloudFunction): Promise
*/
export async function deleteFunction(cloudFunction: string): Promise<Operation> {
try {
const res = await client.delete<Operation>(cloudFunction);
const res = await client.delete<Operation>(cloudFunction, { retryOnPrematureClose: false });
return res.body;
} catch (err: any) {
throw functionsOpLogReject({ name: cloudFunction } as InputCloudFunction, "update", err);
Expand Down
Loading