66 initializeGraphQLTesting ,
77 MockContext ,
88 saveFixtures ,
9+ testQueryErrorCode ,
910} from './helpers' ;
1011import createOrGetConnection from '../src/db' ;
1112import { ArticlePost } from '../src/entity/posts/ArticlePost' ;
@@ -16,14 +17,24 @@ import { Source, SourceType } from '../src/entity/Source';
1617import { HighlightSignificance } from '../src/common/channelHighlight/significance' ;
1718import { PostType } from '../src/entity/posts/Post' ;
1819import { sourcesFixture } from './fixture/source' ;
20+ import { User } from '../src/entity/user/User' ;
21+ import { usersFixture } from './fixture/user' ;
22+ import { NotificationPreferenceSource } from '../src/entity/notifications/NotificationPreferenceSource' ;
23+ import {
24+ NotificationPreferenceStatus ,
25+ NotificationType ,
26+ } from '../src/notifications/common' ;
1927
2028let con : DataSource ;
2129let state : GraphQLTestingState ;
2230let client : GraphQLTestClient ;
31+ let loggedUser : string | null = null ;
2332
2433beforeAll ( async ( ) => {
2534 con = await createOrGetConnection ( ) ;
26- state = await initializeGraphQLTesting ( ( ) => new MockContext ( con ) ) ;
35+ state = await initializeGraphQLTesting (
36+ ( ) => new MockContext ( con , loggedUser ) ,
37+ ) ;
2738 client = state . client ;
2839} ) ;
2940
@@ -87,6 +98,8 @@ const createTestPosts = async () => {
8798
8899beforeEach ( async ( ) => {
89100 jest . resetAllMocks ( ) ;
101+ loggedUser = null ;
102+ await con . getRepository ( NotificationPreferenceSource ) . clear ( ) ;
90103 await con . getRepository ( ChannelDigest ) . clear ( ) ;
91104 await con . getRepository ( ChannelHighlightDefinition ) . clear ( ) ;
92105 await con . getRepository ( HighlightsCanonical ) . clear ( ) ;
@@ -290,6 +303,177 @@ const CHANNEL_DIGEST_CONFIGURATIONS_QUERY = `
290303 }
291304` ;
292305
306+ const DAILY_HIGHLIGHTS_QUERY = `
307+ query DailyHighlights($first: Int, $after: String) {
308+ dailyHighlights(first: $first, after: $after) {
309+ pageInfo {
310+ hasNextPage
311+ endCursor
312+ }
313+ edges {
314+ cursor
315+ node {
316+ id
317+ channel
318+ highlightedAt
319+ headline
320+ significance
321+ post {
322+ id
323+ title
324+ }
325+ }
326+ }
327+ }
328+ }
329+ ` ;
330+
331+ describe ( 'query dailyHighlights' , ( ) => {
332+ const saveDigestSource = ( id : string ) =>
333+ con . getRepository ( Source ) . save ( {
334+ id,
335+ name : id ,
336+ image : `https://example.com/${ id } .png` ,
337+ handle : id ,
338+ type : SourceType . Machine ,
339+ active : true ,
340+ private : false ,
341+ } ) ;
342+
343+ const subscribeToDigest = ( sourceId : string ) =>
344+ con . getRepository ( NotificationPreferenceSource ) . save ( {
345+ userId : '1' ,
346+ referenceId : sourceId ,
347+ sourceId,
348+ notificationType : NotificationType . SourcePostAdded ,
349+ status : NotificationPreferenceStatus . Subscribed ,
350+ } ) ;
351+
352+ beforeEach ( async ( ) => {
353+ await saveFixtures ( con , User , usersFixture ) ;
354+ await createTestPosts ( ) ;
355+ } ) ;
356+
357+ it ( 'should require authentication' , ( ) =>
358+ testQueryErrorCode (
359+ client ,
360+ { query : DAILY_HIGHLIGHTS_QUERY } ,
361+ 'UNAUTHENTICATED' ,
362+ ) ) ;
363+
364+ it ( 'should return the top highlight of the day per subscribed channel' , async ( ) => {
365+ loggedUser = '1' ;
366+
367+ await saveDigestSource ( 'backend_digest' ) ;
368+ await saveDigestSource ( 'career_digest' ) ;
369+ await con . getRepository ( ChannelDigest ) . save ( [
370+ {
371+ key : 'backend' ,
372+ sourceId : 'backend_digest' ,
373+ channel : 'backend' ,
374+ targetAudience : 'backend devs' ,
375+ frequency : 'daily' ,
376+ enabled : true ,
377+ } ,
378+ {
379+ key : 'career' ,
380+ sourceId : 'career_digest' ,
381+ channel : 'career' ,
382+ targetAudience : 'everyone' ,
383+ frequency : 'daily' ,
384+ enabled : true ,
385+ } ,
386+ ] ) ;
387+ await subscribeToDigest ( 'backend_digest' ) ;
388+ await subscribeToDigest ( 'career_digest' ) ;
389+
390+ const today = new Date ( ) ;
391+ today . setUTCHours ( 12 , 0 , 0 , 0 ) ;
392+ const earlierToday = new Date ( today . getTime ( ) - 60 * 60 * 1000 ) ;
393+ const yesterday = new Date ( today . getTime ( ) - 2 * 24 * 60 * 60 * 1000 ) ;
394+
395+ await saveCanonicalHighlights ( [
396+ {
397+ id : '11111111-1111-1111-1111-111111111111' ,
398+ postId : 'h1' ,
399+ channel : 'backend' ,
400+ highlightedAt : today ,
401+ headline : 'Backend major' ,
402+ significance : HighlightSignificance . Major ,
403+ } ,
404+ {
405+ id : '22222222-2222-2222-2222-222222222222' ,
406+ postId : 'h2' ,
407+ channel : 'backend' ,
408+ highlightedAt : earlierToday ,
409+ headline : 'Backend breaking' ,
410+ significance : HighlightSignificance . Breaking ,
411+ } ,
412+ {
413+ id : '33333333-3333-3333-3333-333333333333' ,
414+ postId : 'h3' ,
415+ channel : 'career' ,
416+ highlightedAt : today ,
417+ headline : 'Career notable' ,
418+ significance : HighlightSignificance . Notable ,
419+ } ,
420+ {
421+ id : '44444444-4444-4444-4444-444444444444' ,
422+ postId : 'h4' ,
423+ channel : 'backend' ,
424+ highlightedAt : yesterday ,
425+ headline : 'Backend stale breaking' ,
426+ significance : HighlightSignificance . Breaking ,
427+ } ,
428+ ] ) ;
429+
430+ const res = await client . query ( DAILY_HIGHLIGHTS_QUERY ) ;
431+
432+ expect ( res . errors ) . toBeFalsy ( ) ;
433+ // backend → breaking (beats today's major); career → notable; stale excluded.
434+ // ordered by highlightedAt desc: career @12:00 then backend @11:00.
435+ expect (
436+ res . data . dailyHighlights . edges . map ( ( { node } ) => ( {
437+ channel : node . channel ,
438+ headline : node . headline ,
439+ postId : node . post . id ,
440+ } ) ) ,
441+ ) . toEqual ( [
442+ { channel : 'career' , headline : 'Career notable' , postId : 'h3' } ,
443+ { channel : 'backend' , headline : 'Backend breaking' , postId : 'h2' } ,
444+ ] ) ;
445+ } ) ;
446+
447+ it ( 'should return empty when the user has no subscriptions' , async ( ) => {
448+ loggedUser = '1' ;
449+
450+ await saveDigestSource ( 'backend_digest' ) ;
451+ await con . getRepository ( ChannelDigest ) . save ( {
452+ key : 'backend' ,
453+ sourceId : 'backend_digest' ,
454+ channel : 'backend' ,
455+ targetAudience : 'backend devs' ,
456+ frequency : 'daily' ,
457+ enabled : true ,
458+ } ) ;
459+ await saveCanonicalHighlights ( [
460+ {
461+ id : '55555555-5555-5555-5555-555555555555' ,
462+ postId : 'h1' ,
463+ channel : 'backend' ,
464+ highlightedAt : new Date ( ) ,
465+ headline : 'Backend today' ,
466+ significance : HighlightSignificance . Major ,
467+ } ,
468+ ] ) ;
469+
470+ const res = await client . query ( DAILY_HIGHLIGHTS_QUERY ) ;
471+
472+ expect ( res . errors ) . toBeFalsy ( ) ;
473+ expect ( res . data . dailyHighlights . edges ) . toEqual ( [ ] ) ;
474+ } ) ;
475+ } ) ;
476+
293477describe ( 'query channelDigestConfigurations' , ( ) => {
294478 const saveDigestSource = ( id : string , name : string ) =>
295479 con . getRepository ( Source ) . save ( {
0 commit comments