@@ -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+
11601402describe ( 'query feedByConfig' , ( ) => {
11611403 const variables = {
11621404 first : 10 ,
0 commit comments