Skip to content
Merged
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
156 changes: 156 additions & 0 deletions packages/frontend/__tests__/lib/getLeaderboardAllTime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,162 @@ afterEach(() => {
});

describe("all-time leaderboard freshness queries", () => {
it("uses competition-rank SQL for all-time list and search ranks", async () => {
mockState.pushAwaitedResult([]);
mockState.pushAwaitedResult([{ totalTokens: 0, totalCost: 0, totalSubmissions: 0, uniqueUsers: 0 }]);

await getLeaderboardData("all", 1, 50, "tokens");
const listSqlTexts = serializeSqlCalls();

expect(listSqlTexts.some((text) => text.includes("RANK() OVER (ORDER BY"))).toBe(true);
expect(listSqlTexts.some((text) => text.includes("ROW_NUMBER() OVER"))).toBe(false);

mockState.reset();
mockState.pushAwaitedResult([]);
mockState.pushAwaitedResult([{ count: 0 }]);
mockState.pushAwaitedResult([{ totalTokens: 0, totalCost: 0, totalSubmissions: 0, uniqueUsers: 0 }]);

await getLeaderboardData("all", 1, 50, "tokens", "ali");
const searchSqlTexts = serializeSqlCalls();

expect(searchSqlTexts.some((text) => text.includes("RANK() OVER (ORDER BY"))).toBe(true);
expect(searchSqlTexts.some((text) => text.includes("ROW_NUMBER() OVER"))).toBe(false);
});

it("keeps tied all-time users at the same rank across list, search, and user rank", async () => {
mockState.pushAwaitedResult([
{
rank: 1,
userId: "user-bob",
username: "bob",
displayName: "Bob",
avatarUrl: null,
totalTokens: 5000,
totalCost: 50,
totalActiveTimeMs: 500,
submissionCount: 1,
lastSubmission: "2026-03-12T10:00:00.000Z",
cliVersion: "1.9.0",
schemaVersion: 1,
},
{
rank: 2,
userId: "user-alice",
username: "alice",
displayName: "Alice",
avatarUrl: null,
totalTokens: 3000,
totalCost: 40,
totalActiveTimeMs: 400,
submissionCount: 1,
lastSubmission: "2026-03-12T09:00:00.000Z",
cliVersion: "1.9.0",
schemaVersion: 1,
},
{
rank: 2,
userId: "user-alicia",
username: "alicia",
displayName: "Alicia",
avatarUrl: null,
totalTokens: 3000,
totalCost: 30,
totalActiveTimeMs: 300,
submissionCount: 1,
lastSubmission: "2026-03-12T08:00:00.000Z",
cliVersion: "1.9.0",
schemaVersion: 1,
},
]);
mockState.pushAwaitedResult([
{
totalTokens: 11000,
totalCost: 120,
totalSubmissions: 3,
uniqueUsers: 3,
},
]);

const leaderboard = await getLeaderboardData("all", 1, 50, "tokens");
const aliceListRank = leaderboard.users.find((user) => user.username === "alice")?.rank;
const aliciaListRank = leaderboard.users.find((user) => user.username === "alicia")?.rank;

mockState.reset();
mockState.pushAwaitedResult([
{
rank: 2,
userId: "user-alice",
username: "alice",
displayName: "Alice",
avatarUrl: null,
totalTokens: 3000,
totalCost: 40,
totalActiveTimeMs: 400,
submissionCount: 1,
lastSubmission: "2026-03-12T09:00:00.000Z",
cliVersion: "1.9.0",
schemaVersion: 1,
},
{
rank: 2,
userId: "user-alicia",
username: "alicia",
displayName: "Alicia",
avatarUrl: null,
totalTokens: 3000,
totalCost: 30,
totalActiveTimeMs: 300,
submissionCount: 1,
lastSubmission: "2026-03-12T08:00:00.000Z",
cliVersion: "1.9.0",
schemaVersion: 1,
},
]);
mockState.pushAwaitedResult([{ count: 2 }]);
mockState.pushAwaitedResult([
{
totalTokens: 11000,
totalCost: 120,
totalSubmissions: 3,
uniqueUsers: 3,
},
]);

const searchLeaderboard = await getLeaderboardData("all", 1, 50, "tokens", "ali");
const aliceSearchRank = searchLeaderboard.users.find((user) => user.username === "alice")?.rank;
const aliciaSearchRank = searchLeaderboard.users.find((user) => user.username === "alicia")?.rank;

mockState.reset();
mockState.pushAwaitedResult([
{
id: "user-alice",
username: "alice",
displayName: "Alice",
avatarUrl: null,
},
]);
mockState.pushAwaitedResult([
{
totalTokens: 3000,
totalCost: 40,
totalActiveTimeMs: 400,
submissionCount: 1,
lastSubmission: "2026-03-12T09:00:00.000Z",
cliVersion: "1.9.0",
schemaVersion: 1,
},
]);
mockState.pushAwaitedResult([{ count: 1 }]);

const aliceUserRank = await getUserRank("alice", "all", "tokens");

expect(aliceListRank).toBe(2);
expect(aliciaListRank).toBe(2);
expect(aliceSearchRank).toBe(2);
expect(aliciaSearchRank).toBe(2);
expect(aliceUserRank?.rank).toBe(2);
});

it("uses latest-row scalar subqueries instead of MAX(cliVersion/schemaVersion)", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-12T18:45:00Z"));
Expand Down
47 changes: 47 additions & 0 deletions packages/frontend/__tests__/lib/getUserEmbedStats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,53 @@ beforeEach(() => {
});

describe("user embed data", () => {
it("keeps embed tie-breakers out of the rank window", async () => {
mockState.pushAwaitedResult([
{
id: "user-alice",
username: "alice",
displayName: "Alice",
avatarUrl: null,
totalTokens: 3000,
totalCost: 40,
submissionCount: 1,
updatedAt: new Date("2026-03-12T09:00:00.000Z"),
},
]);
mockState.pushExecuteResult([{ rank: 2, total: 3 }]);

await getUserEmbedStats("alice", "tokens");
const tokenSqlTexts = serializeSqlCalls();

expect(tokenSqlTexts.some((text) => text.includes("RANK() OVER"))).toBe(true);
expect(tokenSqlTexts.some((text) =>
text.includes("total_tokens DESC, CAST(total_cost AS DECIMAL(12,4)) DESC")
)).toBe(false);

mockState.reset();
mockState.pushAwaitedResult([
{
id: "user-alice",
username: "alice",
displayName: "Alice",
avatarUrl: null,
totalTokens: 3000,
totalCost: 40,
submissionCount: 1,
updatedAt: new Date("2026-03-12T09:00:00.000Z"),
},
]);
mockState.pushExecuteResult([{ rank: 2, total: 3 }]);

await getUserEmbedStats("alice", "cost");
const costSqlTexts = serializeSqlCalls();

expect(costSqlTexts.some((text) => text.includes("RANK() OVER"))).toBe(true);
expect(costSqlTexts.some((text) =>
text.includes("CAST(total_cost AS DECIMAL(12,4)) DESC, total_tokens DESC")
)).toBe(false);
});

it("looks up embed stats usernames case-insensitively and returns the canonical username", async () => {
mockState.pushAwaitedResult([
{
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/lib/embed/getUserEmbedStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ async function fetchUserEmbedStats(username: string, sortBy: EmbedSortBy): Promi
RANK() OVER (
ORDER BY
${sortBy === "cost"
? sql`CAST(total_cost AS DECIMAL(12,4)) DESC, total_tokens DESC`
: sql`total_tokens DESC, CAST(total_cost AS DECIMAL(12,4)) DESC`}
? sql`CAST(total_cost AS DECIMAL(12,4)) DESC`
: sql`total_tokens DESC`}
) AS rank
FROM submissions
)
Expand Down
32 changes: 25 additions & 7 deletions packages/frontend/src/lib/leaderboard/getLeaderboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,13 +347,18 @@ async function fetchLeaderboardData(
: sortBy === "time"
? sql`COALESCE(SUM(${submissions.totalActiveTimeMs}), 0)`
: sql`SUM(${submissions.totalTokens})`;
const secondaryOrderByColumn = sortBy === "cost"
? sql`SUM(${submissions.totalTokens})`
: sortBy === "time"
? sql`SUM(${submissions.totalTokens})`
: sql`SUM(CAST(${submissions.totalCost} AS DECIMAL(12,4)))`;

if (search) {
// When searching, use a subquery to compute global ranks for ALL users,
// then filter by username. This preserves each user's true rank.
const rankedSubquery = db
.select({
rank: sql<number>`ROW_NUMBER() OVER (ORDER BY ${orderByColumn} DESC)`.as("rank"),
rank: sql<number>`RANK() OVER (ORDER BY ${orderByColumn} DESC)`.as("rank"),
userId: users.id,
username: users.username,
displayName: users.displayName,
Expand All @@ -378,14 +383,23 @@ async function fetchLeaderboardData(
.innerJoin(users, eq(submissions.userId, users.id))
.groupBy(users.id, users.username, users.displayName, users.avatarUrl)
.as("ranked");
const rankedSecondaryOrderByColumn = sortBy === "cost"
? rankedSubquery.totalTokens
: sortBy === "time"
? rankedSubquery.totalTokens
: rankedSubquery.totalCost;

const escapedSearch = search.toLowerCase().replace(/[%_\\]/g, "\\$&");
const searchPattern = `%${escapedSearch}%`;
const results = await db
.select()
.from(rankedSubquery)
.where(sql`(LOWER(${rankedSubquery.username}) LIKE ${searchPattern} OR LOWER(COALESCE(${rankedSubquery.displayName}, '')) LIKE ${searchPattern})`)
.orderBy(sql`${rankedSubquery.rank} ASC`)
.orderBy(
sql`${rankedSubquery.rank} ASC`,
sql`${rankedSecondaryOrderByColumn} DESC`,
sql`LOWER(${rankedSubquery.username}) ASC`
)
.limit(limit)
.offset(offset);

Expand Down Expand Up @@ -446,10 +460,10 @@ async function fetchLeaderboardData(
};
}

// Non-search path: original query with sequential rank
// Non-search path: competition rank with deterministic row ordering for ties.
const leaderboardQuery = db
.select({
rank: sql<number>`ROW_NUMBER() OVER (ORDER BY ${orderByColumn} DESC)`.as("rank"),
rank: sql<number>`RANK() OVER (ORDER BY ${orderByColumn} DESC)`.as("rank"),
userId: users.id,
username: users.username,
displayName: users.displayName,
Expand All @@ -473,7 +487,11 @@ async function fetchLeaderboardData(
.from(submissions)
.innerJoin(users, eq(submissions.userId, users.id))
.groupBy(users.id, users.username, users.displayName, users.avatarUrl)
.orderBy(desc(orderByColumn))
.orderBy(
desc(orderByColumn),
desc(secondaryOrderByColumn),
sql`LOWER(${users.username}) ASC`
)
.limit(limit)
.offset(offset);

Expand All @@ -493,8 +511,8 @@ async function fetchLeaderboardData(
const totalPages = Math.ceil(totalUsers / limit);

return {
users: (results as AllTimeLeaderboardDbRow[]).map((row, index) => ({
rank: offset + index + 1,
users: (results as RankedLeaderboardDbRow[]).map((row) => ({
rank: Number(row.rank),
userId: row.userId,
username: row.username,
displayName: row.displayName,
Expand Down