Skip to content
Open
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
2 changes: 1 addition & 1 deletion .infra/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ debezium.source.database.user=%database_user%
debezium.source.database.password=%database_pass%
debezium.source.database.dbname=%database_dbname%
debezium.source.database.server.name=api
debezium.source.table.include.list=public.comment,public.user_comment,public.comment_mention,public.source_request,public.post,public.user,public.post_report,public.source_feed,public.settings,public.reputation_event,public.submission,public.user_state,public.notification_v2,public.source_member,public.feature,public.source,public.post_mention,public.content_image,public.comment_report,public.user_post,public.banner,public.post_relation,public.marketing_cta,public.squad_public_request,public.user_streak,public.bookmark,public.user_company,public.source_report,public.user_top_reader,public.source_post_moderation,public.user_report,public.user_transaction,public.content_preference,public.campaign,public.opportunity_match,public.opportunity,public.organization,public.user_candidate_preference,public.user_experience,public.feedback
debezium.source.table.include.list=public.comment,public.user_comment,public.comment_mention,public.source_request,public.post,public.user,public.post_report,public.source_feed,public.settings,public.reputation_event,public.submission,public.user_state,public.notification_v2,public.source_member,public.feature,public.source,public.post_mention,public.content_image,public.comment_report,public.user_post,public.banner,public.post_relation,public.marketing_cta,public.squad_public_request,public.user_streak,public.bookmark,public.user_company,public.source_report,public.user_top_reader,public.source_post_moderation,public.user_report,public.user_transaction,public.content_preference,public.campaign,public.opportunity_match,public.opportunity,public.organization,public.user_candidate_preference,public.user_experience,public.feedback,public.hot_take,public.user_stack
debezium.source.column.exclude.list=public.post.tsv,public.post.placeholder,public.source.flags,public.user_top_reader.image
debezium.source.skip.messages.without.change=true
debezium.source.plugin.name=pgoutput
Expand Down
4 changes: 4 additions & 0 deletions .infra/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,10 @@ export const workers: Worker[] = [
topic: 'api.v1.feedback-updated',
subscription: 'api.feedback-updated-slack',
},
{
topic: 'api.v1.achievement-unlocked',
subscription: 'api.achievement-unlocked-notification',
},
];

export const personalizedDigestWorkers: Worker[] = [
Expand Down
111 changes: 111 additions & 0 deletions __tests__/workers/newView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@ import {
} from '../helpers';
import { postsFixture } from '../fixture/post';
import {
Achievement,
AchievementEventType,
AchievementType,
Alerts,
ArticlePost,
BRIEFING_SOURCE,
PostType,
Source,
User,
UserStreak,
UserStreakAction,
UserStreakActionType,
View,
} from '../../src/entity';
import { BriefPost } from '../../src/entity/posts/BriefPost';
import { UserAchievement } from '../../src/entity/user/UserAchievement';
import { sourcesFixture } from '../fixture/source';
import { usersFixture } from '../fixture/user';
import { DataSource, IsNull, Not } from 'typeorm';
Expand Down Expand Up @@ -602,3 +609,107 @@ describe('reading streaks', () => {
});
});
});

describe('brief read achievement', () => {
const briefAchievementId = 'debriefed-achievement-id';

beforeEach(async () => {
// Seed the brief read achievement
await con.getRepository(Achievement).save({
id: briefAchievementId,
name: 'Debriefed',
description: 'Read 5 briefs',
image: 'https://example.com/achievement.png',
type: AchievementType.Milestone,
eventType: AchievementEventType.BriefRead,
criteria: { targetCount: 5 },
points: 15,
});

// Create the briefing source if it doesn't exist
await con.getRepository(Source).save({
id: BRIEFING_SOURCE,
name: 'Briefing',
handle: 'briefing',
private: true,
});

// Create a brief post
await con.getRepository(BriefPost).save({
id: 'brief-1',
shortId: 'brief1',
sourceId: BRIEFING_SOURCE,
authorId: 'u1',
title: 'Test Brief',
type: PostType.Brief,
private: true,
visible: true,
});
});

it('should increment BriefRead achievement progress when viewing a BriefPost', async () => {
await expectSuccessfulBackground(worker, {
postId: 'brief-1',
userId: 'u2',
referer: 'referer',
timestamp: new Date().toISOString(),
});

const userAchievement = await con.getRepository(UserAchievement).findOne({
where: { userId: 'u2', achievementId: briefAchievementId },
});

expect(userAchievement).not.toBeNull();
expect(userAchievement?.progress).toBe(1);
expect(userAchievement?.unlockedAt).toBeNull();
});

it('should unlock BriefRead achievement after reading 5 briefs', async () => {
// Create 5 brief posts
for (let i = 2; i <= 5; i++) {
await con.getRepository(BriefPost).save({
id: `brief-${i}`,
shortId: `brief${i}`,
sourceId: BRIEFING_SOURCE,
authorId: 'u1',
title: `Test Brief ${i}`,
type: PostType.Brief,
private: true,
visible: true,
});
}

// View all 5 briefs
for (let i = 1; i <= 5; i++) {
await expectSuccessfulBackground(worker, {
postId: `brief-${i}`,
userId: 'u2',
referer: 'referer',
timestamp: new Date().toISOString(),
});
}

const userAchievement = await con.getRepository(UserAchievement).findOne({
where: { userId: 'u2', achievementId: briefAchievementId },
});

expect(userAchievement).not.toBeNull();
expect(userAchievement?.progress).toBe(5);
expect(userAchievement?.unlockedAt).not.toBeNull();
});

it('should NOT trigger BriefRead achievement when viewing an article post', async () => {
await expectSuccessfulBackground(worker, {
postId: 'p1',
userId: 'u1',
referer: 'referer',
timestamp: new Date().toISOString(),
});

const userAchievement = await con.getRepository(UserAchievement).findOne({
where: { userId: 'u1', achievementId: briefAchievementId },
});

expect(userAchievement).toBeNull();
});
});
Loading
Loading