Skip to content
Merged
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
52 changes: 51 additions & 1 deletion functions/scripts/scenario-many-users.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
* SIM_ONLY a comma-separated list of what to run besides the accounts,
* pets and posts the rest need: follows, likes, comments,
* meetup, invitation, places, visitors, ratelimit, names,
* deletes, family.
* deletes, family, petnames.
* SIM_CLEANUP 1 to delete everything the run made, and its accounts,
* then check that nothing of it is left. Always on in the
* cloud.
Expand Down Expand Up @@ -862,6 +862,56 @@ async function scenarios() {
}
}

if (want("petnames")) {
// --- A pet renamed and given a new photo twice in a row by its owner,
// while everyone else follows it and it is on a meetup's roster: every
// copy of its name and photo ends with the last ones, and its post count
// does not move.
const owner = users[1];
const star = petIds[1];
const follows = await Promise.all(users.filter((u) => u !== owner).map((u) => call(u, "followPetCallable", { petId: star })));
const gathering = await call(owner, "createMeetupCallable", {
title: `TEST CONTENT ${TAG} with a renamed pet`, description: "TEST CONTENT the pet is renamed",
dateMillis: Date.now() + 5 * 86_400_000, duration: 60,
location: { kind: "address", address: "1 Sim St, Medford, MA", label: "" },
locationVisibility: "everyone",
requirements: { petType: "any", dogSize: "any", maxPets: 0, mustHavePosts: false, mustHavePetProfile: false, minFollowers: 0, additionalNotes: "" },
organizerPetId: star,
});
const gatheringId = gathering.result?.meetupId ?? gathering.result?.id;
if (!gatheringId) throw new Error(`the meetup came back without an id: ${JSON.stringify(gathering).slice(0, 200)}`);
made.meetups.push(gatheringId);
const petCopies = async () => ({
posts: await adminQuery(null, "posts", { where: ["petId", star] }),
follows: await adminQuery(null, "followingPets", { allDescendants: true, where: ["petId", star] }),
roster: (await adminQuery(`meetups/${gatheringId}`, "participants")).filter((r) => r.petId === star),
});
const outOfStep = (c, name, photo) => [
...c.posts.filter((d) => d.petName !== name || d.petAvatarUrl !== photo).map(() => "posts"),
...c.follows.filter((d) => d.petName !== name || d.petAvatar !== photo).map(() => "follow lists"),
...c.roster.filter((d) => d.petName !== name || d.petAvatar !== photo).map(() => "roster"),
];
// Every follow is in place before the renames start.
await settle(petCopies, (c) => c.follows.length === tally(follows).ok);
const postCount = (await adminGet(`pets/${star}`))?.postCount;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Settle the post count before capturing the baseline

When post aggregation is delayed, this read can capture the pet's initial or partially updated postCount, because createPostCallable creates posts with an empty contribution and onPostWritten updates the pet asynchronously. The later synchronization wait gives that trigger time to finish, after which the final comparison reports that renaming changed the count even though only the earlier post events did. Settle this pet's count to the expected number of existing posts before recording the baseline.

Useful? React with 馃憤聽/ 馃憥.

const photo = (k) => `https://api.dicebear.com/7.x/thumbs/svg?seed=${TAG}-${k}`;
const steps = [];
for (const k of [1, 2]) steps.push(await call(owner, "updatePetCallable", { petId: star, name: `Simpet ${SHORT} v${k}`, avatarUrl: photo(k) }));
const lastName = `Simpet ${SHORT} v2`;
const end = await settle(petCopies, (c) => outOfStep(c, lastName, photo(2)).length === 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the pet propagation trigger before enabling this scenario

With the reviewed commit alone, the documented default emulator run always times out here and exits unsuccessfully: a repo-wide search finds no onPetUpdated handler, and functions/src/index.ts exports no pet-update trigger, while updatePetCallable only updates pets/{petId}. Consequently the existing posts, follow-list entries, and participant record can never acquire lastName or photo(2). The propagation implementation needs to land with this scenario, or the scenario must not run by default until that dependency exists.

Useful? React with 馃憤聽/ 馃憥.

const left = outOfStep(end, lastName, photo(2));
const total = end.posts.length + end.follows.length + end.roster.length;
const byKind = Object.entries(left.reduce((m, kind) => ({ ...m, [kind]: (m[kind] ?? 0) + 1 }), {}))
.map(([kind, n]) => `${n} ${kind}`).join(", ");
check("pets: renamed with a new photo twice in a row, every copy ends with the last",
steps.every((st) => st.ok) && end.follows.length === tally(follows).ok && end.roster.length === 1 && left.length === 0,
Comment on lines +906 to +907

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require every follower request to succeed

If any followPetCallable requests fail鈥攆or example because of a regression or transient callable refusal鈥攖he assertion still passes with fewer follow-list copies because it only compares the resulting count with tally(follows).ok. Even zero successful follows can pass once the posts and roster are synchronized, so this does not enforce the stated scenario that everyone except the owner follows the pet; include tally(follows).ok === N - 1 in the result.

Useful? React with 馃憤聽/ 馃憥.

`${steps.filter((st) => st.ok).length} of 2 changes ok${steps.every((st) => st.ok) ? "" : ` (${tally(steps).codes})`}; `
+ `${total - left.length} of ${total} copies say the last name and photo (${end.posts.length} posts, ${end.follows.length} follow lists, ${end.roster.length} roster)`
+ `${left.length ? `; an older one: ${byKind}` : ""}`);
const renamed = await adminGet(`pets/${star}`);
check("pets: and its post count does not move", renamed?.postCount === postCount, `postCount ${renamed?.postCount}, before ${postCount}`);
}

if (want("deletes")) {
// --- A post deleted while everyone is liking and commenting on it, and a
// pet deleted while everyone is following it. Nothing of either may be
Expand Down
Loading