musora-content-services 2.103.6 → 2.104.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +18 -0
- package/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/conventional-commits.yaml +0 -0
- package/.github/workflows/docs.js.yml +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/CHANGELOG.md +24 -0
- package/CLAUDE.md +0 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/package.json +1 -1
- package/src/constants/award-assets.js +0 -0
- package/src/contentMetaData.js +0 -0
- package/src/contentTypeConfig.js +1 -1
- package/src/index.d.ts +3 -1
- package/src/index.js +3 -1
- package/src/infrastructure/http/HttpClient.ts +0 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
- package/src/infrastructure/http/index.ts +0 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
- package/src/lib/brands.ts +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/services/api/types.js +0 -0
- package/src/services/api/types.ts +0 -0
- package/src/services/awards/award-query.js +13 -0
- package/src/services/awards/internal/.indexignore +0 -0
- package/src/services/awards/internal/award-definitions.js +0 -0
- package/src/services/awards/internal/award-events.js +0 -0
- package/src/services/awards/internal/award-manager.js +5 -0
- package/src/services/awards/internal/certificate-builder.js +0 -0
- package/src/services/awards/internal/completion-data-generator.js +0 -0
- package/src/services/awards/internal/content-progress-observer.js +0 -0
- package/src/services/awards/internal/image-utils.js +0 -0
- package/src/services/awards/internal/message-generator.js +0 -0
- package/src/services/awards/internal/types.js +0 -0
- package/src/services/awards/types.js +0 -0
- package/src/services/config.js +0 -0
- package/src/services/content/content.ts +0 -0
- package/src/services/content-org/content-org.js +0 -0
- package/src/services/content-org/guided-courses.ts +0 -0
- package/src/services/content-org/playlists-types.js +0 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/content.js +0 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/dataContext.js +0 -0
- package/src/services/dateUtils.js +0 -0
- package/src/services/eventsAPI.js +0 -0
- package/src/services/forums/categories.ts +0 -0
- package/src/services/forums/forums.ts +2 -1
- package/src/services/forums/posts.ts +0 -0
- package/src/services/forums/types.ts +0 -0
- package/src/services/gamification/awards.ts +0 -0
- package/src/services/gamification/gamification.js +0 -0
- package/src/services/imageSRCBuilder.js +0 -0
- package/src/services/imageSRCVerify.js +0 -0
- package/src/services/liveTesting.ts +0 -0
- package/src/services/permissions/PermissionsAdapter.ts +0 -0
- package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
- package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
- package/src/services/permissions/README.md +0 -0
- package/src/services/permissions/index.ts +0 -0
- package/src/services/progress-row/method-card.js +0 -0
- package/src/services/recommendations.js +0 -0
- package/src/services/reporting/README.md +0 -0
- package/src/services/reporting/reporting.ts +0 -0
- package/src/services/reporting/types.ts +0 -0
- package/src/services/sentry/.indexignore +0 -0
- package/src/services/sentry/index.ts +0 -0
- package/src/services/sync/.indexignore +0 -0
- package/src/services/sync/adapters/factory.ts +0 -0
- package/src/services/sync/adapters/lokijs.ts +0 -0
- package/src/services/sync/adapters/sqlite.ts +0 -0
- package/src/services/sync/concurrency-safety.ts +0 -0
- package/src/services/sync/context/index.ts +0 -0
- package/src/services/sync/context/providers/base.ts +0 -0
- package/src/services/sync/context/providers/connectivity.ts +0 -0
- package/src/services/sync/context/providers/durability.ts +0 -0
- package/src/services/sync/context/providers/index.ts +0 -0
- package/src/services/sync/context/providers/session.ts +0 -0
- package/src/services/sync/context/providers/tabs.ts +0 -0
- package/src/services/sync/context/providers/visibility.ts +0 -0
- package/src/services/sync/database/factory.ts +0 -0
- package/src/services/sync/errors/boundary.ts +0 -0
- package/src/services/sync/errors/index.ts +0 -0
- package/src/services/sync/index.ts +0 -0
- package/src/services/sync/models/Base.ts +0 -0
- package/src/services/sync/models/ContentLike.ts +0 -0
- package/src/services/sync/models/Practice.ts +0 -0
- package/src/services/sync/models/PracticeDayNote.ts +0 -0
- package/src/services/sync/models/UserAwardProgress.ts +0 -0
- package/src/services/sync/models/index.ts +0 -0
- package/src/services/sync/repositories/base.ts +0 -0
- package/src/services/sync/repositories/content-likes.ts +0 -0
- package/src/services/sync/repositories/index.ts +0 -0
- package/src/services/sync/repositories/practice-day-notes.ts +0 -0
- package/src/services/sync/repositories/practices.ts +0 -0
- package/src/services/sync/repositories/user-award-progress.ts +26 -0
- package/src/services/sync/repository-proxy.ts +0 -0
- package/src/services/sync/resolver.ts +0 -0
- package/src/services/sync/retry.ts +0 -0
- package/src/services/sync/run-scope.ts +0 -0
- package/src/services/sync/schema/index.ts +0 -0
- package/src/services/sync/serializers/index.ts +0 -0
- package/src/services/sync/serializers/model.ts +0 -0
- package/src/services/sync/serializers/raw.ts +0 -0
- package/src/services/sync/store/push-coalescer.ts +0 -0
- package/src/services/sync/store-configs.ts +0 -0
- package/src/services/sync/strategies/base.ts +0 -0
- package/src/services/sync/strategies/index.ts +0 -0
- package/src/services/sync/strategies/initial.ts +0 -0
- package/src/services/sync/strategies/polling.ts +0 -0
- package/src/services/sync/telemetry/index.ts +0 -0
- package/src/services/sync/telemetry/sampling.ts +0 -0
- package/src/services/sync/utils/event-emitter.ts +0 -0
- package/src/services/sync/utils/index.ts +0 -0
- package/src/services/sync/utils/throttle.ts +0 -0
- package/src/services/sync/utils/timers.ts +0 -0
- package/src/services/types.js +0 -0
- package/src/services/user/account.ts +0 -0
- package/src/services/user/chat.js +0 -0
- package/src/services/user/interests.js +0 -0
- package/src/services/user/management.js +0 -0
- package/src/services/user/notifications.js +0 -0
- package/src/services/user/payments.ts +0 -0
- package/src/services/user/permissions.js +0 -0
- package/src/services/user/profile.js +0 -0
- package/src/services/user/types.d.ts +0 -0
- package/src/services/user/types.js +0 -0
- package/src/services/user/user-management-system.js +0 -0
- package/test/HttpClient.test.js +0 -0
- package/test/awards/award-alacarte-observer.test.js +0 -0
- package/test/awards/award-auto-refresh.test.js +0 -0
- package/test/awards/award-calculations.test.js +0 -0
- package/test/awards/award-certificate-display.test.js +0 -0
- package/test/awards/award-collection-edge-cases.test.js +0 -0
- package/test/awards/award-collection-filtering.test.js +0 -0
- package/test/awards/award-completion-flow.test.js +90 -0
- package/test/awards/award-exclusion-handling.test.js +0 -0
- package/test/awards/award-multi-lesson.test.js +0 -0
- package/test/awards/award-observer-integration.test.js +0 -0
- package/test/awards/award-query-messages.test.js +0 -0
- package/test/awards/award-user-collection.test.js +0 -0
- package/test/awards/duplicate-prevention.test.js +0 -0
- package/test/awards/helpers/completion-mock.js +0 -0
- package/test/awards/helpers/index.js +0 -0
- package/test/awards/helpers/mock-setup.js +0 -0
- package/test/awards/helpers/progress-emitter.js +0 -0
- package/test/awards/message-generator.test.js +0 -0
- package/test/content.test.js +0 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/award-definitions.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_progress_content.json +0 -0
- package/test/mockData/mockData_sanity_progress_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/notifications.test.js +0 -0
- package/test/progressRows.test.js +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/sync/models/award-database-integration.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- package/tools/generate-index.cjs +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(find:*)",
|
|
5
|
+
"Bash(docker exec:*)",
|
|
6
|
+
"Bash(npm test:*)",
|
|
7
|
+
"WebSearch",
|
|
8
|
+
"WebFetch(domain:watermelondb.dev)",
|
|
9
|
+
"WebFetch(domain:github.com)",
|
|
10
|
+
"Bash(git checkout:*)",
|
|
11
|
+
"Bash(npm run doc:*)",
|
|
12
|
+
"Bash(cat:*)",
|
|
13
|
+
"Bash(tr:*)"
|
|
14
|
+
],
|
|
15
|
+
"deny": [],
|
|
16
|
+
"ask": []
|
|
17
|
+
}
|
|
18
|
+
}
|
package/.coderabbit.yaml
CHANGED
|
File without changes
|
package/.editorconfig
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/.prettierignore
CHANGED
|
File without changes
|
package/.prettierrc
CHANGED
|
File without changes
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [2.104.2](https://github.com/railroadmedia/musora-content-services/compare/v2.104.1...v2.104.2) (2025-12-12)
|
|
6
|
+
|
|
7
|
+
### [2.104.1](https://github.com/railroadmedia/musora-content-services/compare/v2.104.0...v2.104.1) (2025-12-12)
|
|
8
|
+
|
|
9
|
+
## [2.104.0](https://github.com/railroadmedia/musora-content-services/compare/v2.100.3...v2.104.0) (2025-12-12)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* add isCompleted and field name updates in award callback([#648](https://github.com/railroadmedia/musora-content-services/issues/648)) ([c250d61](https://github.com/railroadmedia/musora-content-services/commit/c250d61b90bc29504827e8fa368a78b34a3f0c08))
|
|
15
|
+
* **auth:** authenticate via auth key ([#644](https://github.com/railroadmedia/musora-content-services/issues/644)) ([122ba63](https://github.com/railroadmedia/musora-content-services/commit/122ba63572e95faf49944a3e819b768cb0a9ab85))
|
|
16
|
+
* **BEH-1457:** method progress duplication ([#617](https://github.com/railroadmedia/musora-content-services/issues/617)) ([917cb9d](https://github.com/railroadmedia/musora-content-services/commit/917cb9dedce2a21c64af4e39eef013a30b236e77))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **agi:** offset default and slug clause on artistBySlug ([10cc6d0](https://github.com/railroadmedia/musora-content-services/commit/10cc6d0390a12715aeba01c5dc72ecdb53d0f236))
|
|
22
|
+
* **agi:** remove count from AGI lessons functions ([5cf3bc3](https://github.com/railroadmedia/musora-content-services/commit/5cf3bc3ade98faa5200cd64ce4b0a9edaff29d7a))
|
|
23
|
+
* **agi:** rollback total on fetchAGILessons ([a02be05](https://github.com/railroadmedia/musora-content-services/commit/a02be0592dd8b195da7971f13ea062674510c8bb))
|
|
24
|
+
* lp lesson upserting ([#645](https://github.com/railroadmedia/musora-content-services/issues/645)) ([1a3094d](https://github.com/railroadmedia/musora-content-services/commit/1a3094d569bb868d62844755b876cee8bc63a365))
|
|
25
|
+
* mobile login broken ([785e0fc](https://github.com/railroadmedia/musora-content-services/commit/785e0fc1fc1ce58840d0e85874f68b9c77342290))
|
|
26
|
+
* **MU2-1305:** fix url and add postId for active discussions ([ac42889](https://github.com/railroadmedia/musora-content-services/commit/ac4288955e2d7c817e0a51aaf1364b75aec6eea1))
|
|
27
|
+
* user id missing on auth key ([ba4068f](https://github.com/railroadmedia/musora-content-services/commit/ba4068ff3aa8005b1b3ed615b07d4ff7eb9f751b))
|
|
28
|
+
|
|
5
29
|
### [2.103.6](https://github.com/railroadmedia/musora-content-services/compare/v2.103.5...v2.103.6) (2025-12-12)
|
|
6
30
|
|
|
7
31
|
### [2.103.5](https://github.com/railroadmedia/musora-content-services/compare/v2.103.4...v2.103.5) (2025-12-12)
|
package/CLAUDE.md
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/babel.config.cjs
CHANGED
|
File without changes
|
package/jest.config.js
CHANGED
|
File without changes
|
package/jsdoc.json
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
File without changes
|
package/src/contentMetaData.js
CHANGED
|
File without changes
|
package/src/contentTypeConfig.js
CHANGED
|
@@ -862,7 +862,7 @@ export function getChildFieldsForContentType(contentType, asQueryString = true)
|
|
|
862
862
|
export function getFieldsForContentType(contentType, asQueryString = true) {
|
|
863
863
|
const fields = contentType
|
|
864
864
|
? DEFAULT_FIELDS.concat(contentTypeConfig?.[contentType]?.fields ?? [])
|
|
865
|
-
: DEFAULT_FIELDS
|
|
865
|
+
: DEFAULT_FIELDS.slice() // ensure copy, not original reference
|
|
866
866
|
return asQueryString ? fields.toString() + ',' : fields
|
|
867
867
|
}
|
|
868
868
|
|
package/src/index.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
getAwardStatistics,
|
|
10
10
|
getCompletedAwards,
|
|
11
11
|
getContentAwards,
|
|
12
|
-
getInProgressAwards
|
|
12
|
+
getInProgressAwards,
|
|
13
|
+
resetAllAwards
|
|
13
14
|
} from './services/awards/award-query.js';
|
|
14
15
|
|
|
15
16
|
import {
|
|
@@ -683,6 +684,7 @@ declare module 'musora-content-services' {
|
|
|
683
684
|
reportPlaylist,
|
|
684
685
|
requestEmailChange,
|
|
685
686
|
reset,
|
|
687
|
+
resetAllAwards,
|
|
686
688
|
resetAllLearningPaths,
|
|
687
689
|
resetPassword,
|
|
688
690
|
restoreComment,
|
package/src/index.js
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
getAwardStatistics,
|
|
14
14
|
getCompletedAwards,
|
|
15
15
|
getContentAwards,
|
|
16
|
-
getInProgressAwards
|
|
16
|
+
getInProgressAwards,
|
|
17
|
+
resetAllAwards
|
|
17
18
|
} from './services/awards/award-query.js';
|
|
18
19
|
|
|
19
20
|
import {
|
|
@@ -682,6 +683,7 @@ export {
|
|
|
682
683
|
reportPlaylist,
|
|
683
684
|
requestEmailChange,
|
|
684
685
|
reset,
|
|
686
|
+
resetAllAwards,
|
|
685
687
|
resetAllLearningPaths,
|
|
686
688
|
resetPassword,
|
|
687
689
|
restoreComment,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/lib/brands.ts
CHANGED
|
File without changes
|
package/src/lib/lastUpdated.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -493,3 +493,16 @@ export async function getAwardStatistics(brand = null) {
|
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* @returns {Promise<{ deletedCount: number }>}
|
|
499
|
+
*/
|
|
500
|
+
export async function resetAllAwards() {
|
|
501
|
+
try {
|
|
502
|
+
const result = await db.userAwardProgress.deleteAllAwards()
|
|
503
|
+
return result
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.error('Failed to reset awards:', error)
|
|
506
|
+
return { deletedCount: 0 }
|
|
507
|
+
}
|
|
508
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -129,6 +129,11 @@ export class AwardManager {
|
|
|
129
129
|
const completedCount = completedLessonIds.length
|
|
130
130
|
const progressPercentage = Math.round((completedCount / childIds.length) * 100)
|
|
131
131
|
|
|
132
|
+
if (progressPercentage === 100) {
|
|
133
|
+
await this.grantAward(award, collection)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
132
137
|
const progressData = {
|
|
133
138
|
completedLessonIds,
|
|
134
139
|
totalLessons: childIds.length,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/services/config.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/services/content.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -70,9 +70,10 @@ function transformLatestDiscussions(response: PaginatedResponse<ForumThread>): T
|
|
|
70
70
|
|
|
71
71
|
return {
|
|
72
72
|
id: thread.id,
|
|
73
|
-
url: `forums/
|
|
73
|
+
url: `forums/threads/${thread.category_id}/${thread.id}`,
|
|
74
74
|
title: thread.title,
|
|
75
75
|
post: postContent,
|
|
76
|
+
postId:postId,
|
|
76
77
|
author: {
|
|
77
78
|
id: thread.author!.id,
|
|
78
79
|
name: thread.author!.display_name,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -130,4 +130,30 @@ export default class UserAwardProgressRepository extends SyncRepository<UserAwar
|
|
|
130
130
|
|
|
131
131
|
return { definitions, progress: progressMap }
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
async deleteAllAwards() {
|
|
135
|
+
const allProgress = await this.getAll()
|
|
136
|
+
const ids = allProgress.data.map(p => p.id)
|
|
137
|
+
|
|
138
|
+
if (ids.length === 0) {
|
|
139
|
+
return { deletedCount: 0 }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await this.deleteSome(ids)
|
|
143
|
+
|
|
144
|
+
const response = await this.store.pushRecordIdsImpatiently(ids)
|
|
145
|
+
|
|
146
|
+
if (response && response.ok) {
|
|
147
|
+
await this.store.db.write(async () => {
|
|
148
|
+
const collection = this.store.db.get<UserAwardProgress>(UserAwardProgress.table)
|
|
149
|
+
const deletedRecords = await collection
|
|
150
|
+
.query(Q.where('id', Q.oneOf(ids)))
|
|
151
|
+
.fetch()
|
|
152
|
+
|
|
153
|
+
await Promise.all(deletedRecords.map(record => record.destroyPermanently()))
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { deletedCount: ids.length }
|
|
158
|
+
}
|
|
133
159
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/services/types.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/HttpClient.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -210,4 +210,94 @@ describe('Award Completion Flow - E2E Scenarios', () => {
|
|
|
210
210
|
expect(db.userAwardProgress.recordAwardProgress).not.toHaveBeenCalled()
|
|
211
211
|
})
|
|
212
212
|
})
|
|
213
|
+
|
|
214
|
+
describe('Scenario: Race condition - eligibility check fails but progress reaches 100%', () => {
|
|
215
|
+
const multiLessonAward = getAwardByContentId(417049)
|
|
216
|
+
const parentCourseId = 417049
|
|
217
|
+
|
|
218
|
+
test('grants award with completedAt when updateAwardProgress calculates 100%', async () => {
|
|
219
|
+
let callCount = 0
|
|
220
|
+
|
|
221
|
+
db.contentProgress.getSomeProgressByContentIds.mockImplementation((contentIds) => {
|
|
222
|
+
callCount++
|
|
223
|
+
|
|
224
|
+
if (callCount === 1) {
|
|
225
|
+
const partialRecords = contentIds
|
|
226
|
+
.filter(id => [417045, 417046, 417047].includes(id))
|
|
227
|
+
.map(id => ({
|
|
228
|
+
content_id: id,
|
|
229
|
+
state: 'completed',
|
|
230
|
+
created_at: Math.floor(Date.now() / 1000)
|
|
231
|
+
}))
|
|
232
|
+
return Promise.resolve({ data: partialRecords })
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const allRecords = contentIds.map(id => ({
|
|
236
|
+
content_id: id,
|
|
237
|
+
state: 'completed',
|
|
238
|
+
created_at: Math.floor(Date.now() / 1000)
|
|
239
|
+
}))
|
|
240
|
+
return Promise.resolve({ data: allRecords })
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
await awardManager.onContentCompleted(parentCourseId)
|
|
244
|
+
|
|
245
|
+
expect(db.userAwardProgress.recordAwardProgress).toHaveBeenCalledWith(
|
|
246
|
+
multiLessonAward._id,
|
|
247
|
+
100,
|
|
248
|
+
expect.objectContaining({
|
|
249
|
+
completedAt: expect.any(Number),
|
|
250
|
+
completionData: expect.objectContaining({
|
|
251
|
+
completed_at: expect.any(String)
|
|
252
|
+
}),
|
|
253
|
+
immediate: true
|
|
254
|
+
})
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
expect(listeners.granted).toHaveBeenCalledTimes(1)
|
|
258
|
+
expect(listeners.progress).not.toHaveBeenCalled()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('emits awardGranted event when race condition triggers completion', async () => {
|
|
262
|
+
let callCount = 0
|
|
263
|
+
|
|
264
|
+
db.contentProgress.getSomeProgressByContentIds.mockImplementation((contentIds) => {
|
|
265
|
+
callCount++
|
|
266
|
+
|
|
267
|
+
if (callCount === 1) {
|
|
268
|
+
const partialRecords = contentIds
|
|
269
|
+
.filter(id => [417045, 417046].includes(id))
|
|
270
|
+
.map(id => ({
|
|
271
|
+
content_id: id,
|
|
272
|
+
state: 'completed',
|
|
273
|
+
created_at: Math.floor(Date.now() / 1000)
|
|
274
|
+
}))
|
|
275
|
+
return Promise.resolve({ data: partialRecords })
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const allRecords = contentIds.map(id => ({
|
|
279
|
+
content_id: id,
|
|
280
|
+
state: 'completed',
|
|
281
|
+
created_at: Math.floor(Date.now() / 1000)
|
|
282
|
+
}))
|
|
283
|
+
return Promise.resolve({ data: allRecords })
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
await awardManager.onContentCompleted(parentCourseId)
|
|
287
|
+
|
|
288
|
+
expect(listeners.granted).toHaveBeenCalledTimes(1)
|
|
289
|
+
expect(listeners.granted).toHaveBeenCalledWith(
|
|
290
|
+
expect.objectContaining({
|
|
291
|
+
awardId: multiLessonAward._id,
|
|
292
|
+
definition: expect.objectContaining({
|
|
293
|
+
name: multiLessonAward.name
|
|
294
|
+
}),
|
|
295
|
+
completionData: expect.objectContaining({
|
|
296
|
+
completed_at: expect.any(String)
|
|
297
|
+
}),
|
|
298
|
+
timestamp: expect.any(Number)
|
|
299
|
+
})
|
|
300
|
+
)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
213
303
|
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/content.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/dataContext.test.js
CHANGED
|
File without changes
|
package/test/forum.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/localStorageMock.js
CHANGED
|
File without changes
|
package/test/log.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/tools/generate-index.cjs
CHANGED
|
File without changes
|