diff --git a/CHANGELOG.md b/CHANGELOG.md index 4279575a586..3285c77f5bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Fixes native module issues by removing `fast-crc32c` (#3247, #3239) - Update to Cloud Firestore emulator v1.11.13 which includes a bug fix for documents which are created and deleted in a single transaction. +- Fixes an edge case with nextPageToken in batchGet in Auth Emulator (#3231). diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index b729beb454d..d018dacb433 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -449,7 +449,7 @@ function batchGet( let newPageToken: string | undefined = undefined; // As a non-standard behavior, passing in limit=-1 will return all users. - if (limit >= 0 && users.length > limit) { + if (limit >= 0 && users.length >= limit) { users.length = limit; if (users.length) { newPageToken = users[users.length - 1].localId; diff --git a/src/test/emulators/auth/batch.spec.ts b/src/test/emulators/auth/batch.spec.ts index bff8810f629..74aa4d80b95 100644 --- a/src/test/emulators/auth/batch.spec.ts +++ b/src/test/emulators/auth/batch.spec.ts @@ -63,6 +63,7 @@ describeAuthEmulator("accounts:batchGet", ({ authApi }) => { it("should allow specifying maxResults and pagination", async () => { const user1 = await registerAnonUser(authApi()); const user2 = await registerUser(authApi(), { email: "foo@example.com", password: "foobar" }); + const localIds = [user1.localId, user2.localId].sort(); const nextPageToken = await authApi() .get(`/identitytoolkit.googleapis.com/v1/projects/${PROJECT_ID}/accounts:batchGet`) @@ -71,9 +72,7 @@ describeAuthEmulator("accounts:batchGet", ({ authApi }) => { .then((res) => { expectStatusCode(200, res); expect(res.body.users).to.have.length(1); - expect(res.body.users[0].localId).to.equal( - user1.localId < user2.localId ? user1.localId : user2.localId - ); + expect(res.body.users[0].localId).to.equal(localIds[0]); expect(res.body).to.have.property("nextPageToken").which.is.a("string"); return res.body.nextPageToken as string; @@ -86,9 +85,7 @@ describeAuthEmulator("accounts:batchGet", ({ authApi }) => { .then((res) => { expectStatusCode(200, res); expect(res.body.users).to.have.length(1); - expect(res.body.users[0].localId).to.equal( - user1.localId > user2.localId ? user1.localId : user2.localId - ); + expect(res.body.users[0].localId).to.equal(localIds[1]); // No more accounts after this, so no page token returned. expect(res.body).not.to.have.property("nextPageToken"); @@ -110,6 +107,41 @@ describeAuthEmulator("accounts:batchGet", ({ authApi }) => { expect(res.body).not.to.have.property("nextPageToken"); }); }); + + it("should always return a page token if page is full", async () => { + const user1 = await registerAnonUser(authApi()); + const user2 = await registerUser(authApi(), { email: "foo@example.com", password: "foobar" }); + const localIds = [user1.localId, user2.localId].sort(); + + const nextPageToken = await authApi() + .get(`/identitytoolkit.googleapis.com/v1/projects/${PROJECT_ID}/accounts:batchGet`) + .query({ maxResults: 2 }) // Return first two users only. + .set("Authorization", "Bearer owner") + .then((res) => { + expectStatusCode(200, res); + expect(res.body.users).to.have.length(2); + expect(res.body.users[0].localId).to.equal(localIds[0]); + expect(res.body.users[1].localId).to.equal(localIds[1]); + + // Even if there are no more users after this page, we should still + // return a page token to match production behavior. See: + // https://github.com/firebase/firebase-tools/issues/3231 + expect(res.body).to.have.property("nextPageToken").which.is.a("string"); + return res.body.nextPageToken as string; + }); + + await authApi() + .get(`/identitytoolkit.googleapis.com/v1/projects/${PROJECT_ID}/accounts:batchGet`) + .query({ nextPageToken }) + .set("Authorization", "Bearer owner") + .then((res) => { + expectStatusCode(200, res); + console.log(nextPageToken, res.body.users); + // Empty page with no page token returned. + expect(res.body.users || []).to.have.length(0); + expect(res.body).not.to.have.property("nextPageToken"); + }); + }); }); describeAuthEmulator("accounts:batchCreate", ({ authApi }) => {