126 lines
4.1 KiB
JavaScript
126 lines
4.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { createPlazaEventService } from './plaza-events.mjs';
|
|
import { createPlazaRecommendService, recommendInternals } from './plaza-recommend.mjs';
|
|
|
|
test('plaza events: dwell tiers increase signal weight', () => {
|
|
const { eventSignal } = createPlazaEventService({ query: async () => [[]] }).internals;
|
|
const short = eventSignal({ event_type: 'dwell', dwell_ms: 1000 });
|
|
const medium = eventSignal({ event_type: 'dwell', dwell_ms: 5000 });
|
|
const long = eventSignal({ event_type: 'dwell', dwell_ms: 35000 });
|
|
assert.ok(medium > short);
|
|
assert.ok(long > medium);
|
|
});
|
|
|
|
test('plaza recommend: mmr rerank reduces same-category clustering', () => {
|
|
const { mmrRerank, itemSimilarity } = recommendInternals;
|
|
const items = [
|
|
{
|
|
id: 'a',
|
|
rank_score: 0.95,
|
|
category_slug: 'travel',
|
|
author_id: 'u1',
|
|
tags: ['日本'],
|
|
},
|
|
{
|
|
id: 'b',
|
|
rank_score: 0.93,
|
|
category_slug: 'travel',
|
|
author_id: 'u2',
|
|
tags: ['日本'],
|
|
},
|
|
{
|
|
id: 'c',
|
|
rank_score: 0.9,
|
|
category_slug: 'study-notes',
|
|
author_id: 'u3',
|
|
tags: ['rust'],
|
|
},
|
|
];
|
|
const reranked = mmrRerank(items, { lambda: 0.75, limit: 3 });
|
|
assert.equal(reranked[0].id, 'a');
|
|
assert.notEqual(reranked[1].category_slug, reranked[0].category_slug);
|
|
assert.equal(itemSimilarity(items[0], items[1]), 1);
|
|
});
|
|
|
|
test('plaza recommend: cursor roundtrip preserves offset', () => {
|
|
const { parseRecommendCursor, encodeRecommendCursor } = recommendInternals;
|
|
const encoded = encodeRecommendCursor({ offset: 20, profileVersion: 42 });
|
|
const parsed = parseRecommendCursor(encoded);
|
|
assert.equal(parsed.offset, 20);
|
|
assert.equal(parsed.profileVersion, 42);
|
|
});
|
|
|
|
test('plaza recommend: cold start interleaves categories', () => {
|
|
const { coldStartInterleave } = recommendInternals;
|
|
const items = [
|
|
{ id: 'a1', category_slug: 'travel', hot_score: 90, published_at: 1 },
|
|
{ id: 'a2', category_slug: 'travel', hot_score: 80, published_at: 2 },
|
|
{ id: 'b1', category_slug: 'study-notes', hot_score: 85, published_at: 3 },
|
|
{ id: 'c1', category_slug: 'creative', hot_score: 70, published_at: 4 },
|
|
];
|
|
const ordered = coldStartInterleave(items, 'session-1');
|
|
assert.equal(ordered.length, 4);
|
|
assert.notEqual(ordered[0].category_slug, ordered[1].category_slug);
|
|
});
|
|
|
|
test('plaza recommend: recallSimilar binds seeds before viewer id', async () => {
|
|
let similarParams = null;
|
|
const pool = {
|
|
async query(sql, params) {
|
|
if (sql.includes('FROM plaza_reactions r1')) {
|
|
similarParams = params;
|
|
return [[]];
|
|
}
|
|
if (sql.includes('SELECT tags FROM plaza_posts WHERE id IN')) {
|
|
return [[{ tags: '[]' }]];
|
|
}
|
|
if (sql.includes('FROM plaza_posts pp')) {
|
|
return [[]];
|
|
}
|
|
return [[]];
|
|
},
|
|
};
|
|
const recommend = createPlazaRecommendService(pool, {
|
|
eventService: {
|
|
async getProfile() {
|
|
return {
|
|
categoryWeights: {},
|
|
tagWeights: {},
|
|
authorWeights: {},
|
|
feedCategoryWeights: {},
|
|
likedPostIds: new Set(['seed-post']),
|
|
dislikedPostIds: new Set(),
|
|
dislikedCategorySlugs: new Set(),
|
|
followedAuthorIds: new Set(),
|
|
seenPostIds: new Set(),
|
|
deepSeenPostIds: new Set(),
|
|
eventCount: 1,
|
|
profileVersion: 7,
|
|
};
|
|
},
|
|
},
|
|
formatPostRow(row) {
|
|
return { id: row.id };
|
|
},
|
|
loadViewerReactions: async () => new Map(),
|
|
});
|
|
|
|
await recommend.listRecommendedFeed({
|
|
viewerId: 'viewer-1',
|
|
sessionId: 'session-1',
|
|
limit: 20,
|
|
});
|
|
|
|
assert.deepEqual(similarParams, ['seed-post', 'viewer-1', 80]);
|
|
});
|
|
|
|
test('plaza events: dislike marks category as disliked', () => {
|
|
const { emptyProfile } = createPlazaEventService({ query: async () => [[]] }).internals;
|
|
const profile = emptyProfile();
|
|
profile.dislikedCategorySlugs.add('travel');
|
|
profile.dislikedPostIds.add('post-1');
|
|
assert.ok(profile.dislikedCategorySlugs.has('travel'));
|
|
assert.ok(profile.deepSeenPostIds instanceof Set);
|
|
});
|