Skip to content

Commit 049cd14

Browse files
committed
Add feedV2 highlight items
1 parent 1b330b9 commit 049cd14

8 files changed

Lines changed: 720 additions & 45 deletions

File tree

__tests__/feeds.ts

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Keyword,
1717
MachineSource,
1818
Post,
19+
PostHighlight,
1920
PostKeyword,
2021
PostTag,
2122
PostType,
@@ -1157,6 +1158,247 @@ describe('query feed', () => {
11571158
});
11581159
});
11591160

1161+
describe('query feedV2', () => {
1162+
const variables = {
1163+
ranking: Ranking.POPULARITY,
1164+
first: 10,
1165+
version: 20,
1166+
};
1167+
1168+
const QUERY = `
1169+
query FeedV2($ranking: Ranking, $first: Int, $after: String, $version: Int, $unreadOnly: Boolean, $supportedTypes: [String!], $highlightsLimit: Int, $noAi: Boolean) {
1170+
feedV2(ranking: $ranking, first: $first, after: $after, version: $version, unreadOnly: $unreadOnly, supportedTypes: $supportedTypes, highlightsLimit: $highlightsLimit, noAi: $noAi) {
1171+
pageInfo {
1172+
endCursor
1173+
hasNextPage
1174+
}
1175+
edges {
1176+
cursor
1177+
node {
1178+
__typename
1179+
... on FeedPostItem {
1180+
feedMeta
1181+
post {
1182+
id
1183+
title
1184+
type
1185+
}
1186+
}
1187+
... on FeedHighlightsItem {
1188+
feedMeta
1189+
highlights {
1190+
id
1191+
headline
1192+
post {
1193+
id
1194+
title
1195+
}
1196+
}
1197+
}
1198+
}
1199+
}
1200+
}
1201+
}
1202+
`;
1203+
1204+
it('should not authorize when not logged-in', () =>
1205+
testQueryErrorCode(client, { query: QUERY, variables }, 'UNAUTHENTICATED'));
1206+
1207+
it('should pass highlights_limit only when highlights are supported', async () => {
1208+
loggedUser = '1';
1209+
await saveFeedFixtures();
1210+
1211+
nock('http://localhost:6002')
1212+
.post('/config')
1213+
.reply(200, {
1214+
user_id: '1',
1215+
config: {
1216+
providers: {},
1217+
},
1218+
});
1219+
nock('http://localhost:6000')
1220+
.post('/feed.json', (body) => {
1221+
expect(body.allowed_post_types).toEqual(['article', 'highlight']);
1222+
expect(body.highlights_limit).toEqual(4);
1223+
return true;
1224+
})
1225+
.reply(200, {
1226+
data: [{ post_id: 'p1' }],
1227+
cursor: 'b',
1228+
});
1229+
1230+
const res = await client.query(QUERY, {
1231+
variables: {
1232+
...variables,
1233+
supportedTypes: ['article', 'highlight'],
1234+
highlightsLimit: 4,
1235+
},
1236+
});
1237+
1238+
expect(res.errors).toBeFalsy();
1239+
expect(res.data.feedV2.edges).toHaveLength(1);
1240+
});
1241+
1242+
it('should return mixed post and highlight items', async () => {
1243+
loggedUser = '1';
1244+
await saveFeedFixtures();
1245+
await con.getRepository(PostHighlight).save([
1246+
{
1247+
id: '3c75fab6-e28b-431d-ab54-a927708de085',
1248+
postId: 'p1',
1249+
channel: 'happening-now',
1250+
highlightedAt: new Date('2026-03-19T10:10:00.000Z'),
1251+
headline: 'First highlight',
1252+
},
1253+
{
1254+
id: 'c2e332bf-83ac-4651-8a05-8e19fbefc5ac',
1255+
postId: 'p4',
1256+
channel: 'happening-now',
1257+
highlightedAt: new Date('2026-03-19T10:20:00.000Z'),
1258+
headline: 'Second highlight',
1259+
},
1260+
]);
1261+
1262+
nock('http://localhost:6002')
1263+
.post('/config')
1264+
.reply(200, {
1265+
user_id: '1',
1266+
config: {
1267+
providers: {},
1268+
},
1269+
});
1270+
nock('http://localhost:6000')
1271+
.post('/feed.json')
1272+
.reply(200, {
1273+
data: [
1274+
{ post_id: 'p1', metadata: { p: 'post' } },
1275+
{
1276+
type: 'highlight',
1277+
post_id: '',
1278+
highlight_ids: [
1279+
'3c75fab6-e28b-431d-ab54-a927708de085',
1280+
'c2e332bf-83ac-4651-8a05-8e19fbefc5ac',
1281+
],
1282+
metadata: { p: 'highlight' },
1283+
},
1284+
{ post_id: 'p4' },
1285+
],
1286+
cursor: 'next-cursor',
1287+
});
1288+
1289+
const res = await client.query(QUERY, {
1290+
variables: {
1291+
...variables,
1292+
supportedTypes: ['article', 'highlight'],
1293+
highlightsLimit: 2,
1294+
},
1295+
});
1296+
1297+
expect(res.errors).toBeFalsy();
1298+
expect(res.data.feedV2).toEqual({
1299+
pageInfo: {
1300+
endCursor: 'next-cursor',
1301+
hasNextPage: false,
1302+
},
1303+
edges: [
1304+
{
1305+
cursor: 'next-cursor',
1306+
node: {
1307+
__typename: 'FeedPostItem',
1308+
feedMeta: base64('{"p":"post"}'),
1309+
post: {
1310+
id: 'p1',
1311+
title: 'P1',
1312+
type: 'article',
1313+
},
1314+
},
1315+
},
1316+
{
1317+
cursor: 'next-cursor',
1318+
node: {
1319+
__typename: 'FeedHighlightsItem',
1320+
feedMeta: base64('{"p":"highlight"}'),
1321+
highlights: [
1322+
{
1323+
id: '3c75fab6-e28b-431d-ab54-a927708de085',
1324+
headline: 'First highlight',
1325+
post: {
1326+
id: 'p1',
1327+
title: 'P1',
1328+
},
1329+
},
1330+
{
1331+
id: 'c2e332bf-83ac-4651-8a05-8e19fbefc5ac',
1332+
headline: 'Second highlight',
1333+
post: {
1334+
id: 'p4',
1335+
title: 'P4',
1336+
},
1337+
},
1338+
],
1339+
},
1340+
},
1341+
{
1342+
cursor: 'next-cursor',
1343+
node: {
1344+
__typename: 'FeedPostItem',
1345+
feedMeta: null,
1346+
post: {
1347+
id: 'p4',
1348+
title: 'P4',
1349+
type: 'article',
1350+
},
1351+
},
1352+
},
1353+
],
1354+
});
1355+
});
1356+
1357+
it('should apply the same post filtering as feed for returned post items', async () => {
1358+
loggedUser = '1';
1359+
await saveFeedFixtures();
1360+
await con.getRepository(Post).update({ id: 'p4' }, { banned: true });
1361+
1362+
nock('http://localhost:6002')
1363+
.post('/config')
1364+
.reply(200, {
1365+
user_id: '1',
1366+
config: {
1367+
providers: {},
1368+
},
1369+
});
1370+
nock('http://localhost:6000')
1371+
.post('/feed.json')
1372+
.reply(200, {
1373+
data: [{ post_id: 'p1' }, { post_id: 'p4' }],
1374+
cursor: 'next-cursor',
1375+
});
1376+
1377+
const res = await client.query(QUERY, {
1378+
variables: {
1379+
...variables,
1380+
supportedTypes: ['article'],
1381+
},
1382+
});
1383+
1384+
expect(res.errors).toBeFalsy();
1385+
expect(res.data.feedV2.edges).toEqual([
1386+
{
1387+
cursor: 'next-cursor',
1388+
node: {
1389+
__typename: 'FeedPostItem',
1390+
feedMeta: null,
1391+
post: {
1392+
id: 'p1',
1393+
title: 'P1',
1394+
type: 'article',
1395+
},
1396+
},
1397+
},
1398+
]);
1399+
});
1400+
});
1401+
11601402
describe('query feedByConfig', () => {
11611403
const variables = {
11621404
first: 10,

__tests__/integrations/feed.ts

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ const rawFeedResponse = {
6666
};
6767
const feedResponse: FeedResponse = {
6868
data: [
69-
['1', '{"p":"a"}'],
70-
['2', '{"p":"b"}'],
71-
['3', '{"p":"c"}'],
72-
['4', null],
73-
['5', null],
74-
['6', null],
69+
{ type: 'post', id: '1', feedMeta: '{"p":"a"}' },
70+
{ type: 'post', id: '2', feedMeta: '{"p":"b"}' },
71+
{ type: 'post', id: '3', feedMeta: '{"p":"c"}' },
72+
{ type: 'post', id: '4', feedMeta: null },
73+
{ type: 'post', id: '5', feedMeta: null },
74+
{ type: 'post', id: '6', feedMeta: null },
7575
],
7676
};
7777

@@ -126,12 +126,68 @@ describe('FeedClient', () => {
126126
});
127127
expect(feed).toEqual({
128128
data: [
129-
['1', '{"p":"a","mab":{"test":"da"}}'],
130-
['2', '{"p":"b","mab":{"test":"da"}}'],
131-
['3', '{"p":"c","mab":{"test":"da"}}'],
132-
['4', '{"mab":{"test":"da"}}'],
133-
['5', '{"mab":{"test":"da"}}'],
134-
['6', '{"mab":{"test":"da"}}'],
129+
{
130+
type: 'post',
131+
id: '1',
132+
feedMeta: '{"p":"a","mab":{"test":"da"}}',
133+
},
134+
{
135+
type: 'post',
136+
id: '2',
137+
feedMeta: '{"p":"b","mab":{"test":"da"}}',
138+
},
139+
{
140+
type: 'post',
141+
id: '3',
142+
feedMeta: '{"p":"c","mab":{"test":"da"}}',
143+
},
144+
{
145+
type: 'post',
146+
id: '4',
147+
feedMeta: '{"mab":{"test":"da"}}',
148+
},
149+
{
150+
type: 'post',
151+
id: '5',
152+
feedMeta: '{"mab":{"test":"da"}}',
153+
},
154+
{
155+
type: 'post',
156+
id: '6',
157+
feedMeta: '{"mab":{"test":"da"}}',
158+
},
159+
],
160+
});
161+
});
162+
163+
it('should preserve highlight items from the feed service response', async () => {
164+
nock(url)
165+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
166+
.post('', config as any)
167+
.reply(200, {
168+
data: [
169+
{ post_id: '1', metadata: { p: 'a' } },
170+
{
171+
type: 'highlight',
172+
post_id: '',
173+
highlight_ids: ['h1', 'h2'],
174+
metadata: { p: 'highlight' },
175+
},
176+
],
177+
});
178+
179+
const feedClient = new FeedClient(url);
180+
const feed = await feedClient.fetchFeed(ctx, 'id', config);
181+
182+
expect(feed).toEqual({
183+
data: [
184+
{ type: 'post', id: '1', feedMeta: '{"p":"a"}' },
185+
{
186+
type: 'highlight',
187+
postId: null,
188+
highlightIds: ['h1', 'h2'],
189+
feedMeta: '{"p":"highlight"}',
190+
},
135191
],
136192
});
137193
});
@@ -176,8 +232,8 @@ describe('connectionFromNodes with staleCursor', () => {
176232
const page = { limit: 30 };
177233
const queryParams: FeedResponse = {
178234
data: [
179-
['1', null],
180-
['2', null],
235+
{ type: 'post', id: '1', feedMeta: null },
236+
{ type: 'post', id: '2', feedMeta: null },
181237
],
182238
cursor: 'next-cursor',
183239
staleCursor: true,
@@ -201,8 +257,8 @@ describe('connectionFromNodes with staleCursor', () => {
201257
const page = { limit: 30 };
202258
const queryParams: FeedResponse = {
203259
data: [
204-
['1', null],
205-
['2', null],
260+
{ type: 'post', id: '1', feedMeta: null },
261+
{ type: 'post', id: '2', feedMeta: null },
206262
],
207263
cursor: 'next-cursor',
208264
};

0 commit comments

Comments
 (0)