musora-content-services 2.117.8 → 2.118.1

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.
Files changed (225) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/.coderabbit.yaml +0 -0
  3. package/.editorconfig +0 -0
  4. package/.github/pull_request_template.md +0 -0
  5. package/.github/workflows/conventional-commits.yaml +0 -0
  6. package/.github/workflows/docs.js.yml +0 -0
  7. package/.github/workflows/node.js.yml +0 -0
  8. package/.prettierignore +0 -0
  9. package/.prettierrc +0 -0
  10. package/.yarnrc.yml +1 -0
  11. package/CHANGELOG.md +45 -0
  12. package/CLAUDE.md +0 -0
  13. package/README.md +0 -0
  14. package/babel.config.cjs +0 -0
  15. package/check_content.js +30 -0
  16. package/check_content.mjs +32 -0
  17. package/jsdoc.json +0 -0
  18. package/package.json +1 -1
  19. package/src/constants/award-assets.js +0 -0
  20. package/src/constants/membership-permissions.ts +0 -0
  21. package/src/contentMetaData.js +9 -5
  22. package/src/filterBuilder.js +0 -0
  23. package/src/index.d.ts +2 -0
  24. package/src/index.js +2 -0
  25. package/src/infrastructure/http/HttpClient.ts +0 -0
  26. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  27. package/src/infrastructure/http/index.ts +0 -0
  28. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  29. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  30. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  31. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  32. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  33. package/src/lib/ads/monoid.ts +0 -0
  34. package/src/lib/ads/semigroup.ts +0 -0
  35. package/src/lib/brands.ts +0 -0
  36. package/src/lib/lastUpdated.js +0 -0
  37. package/src/lib/sanity/filter.ts +0 -0
  38. package/src/lib/sanity/query.ts +0 -0
  39. package/src/services/api/types.js +0 -0
  40. package/src/services/api/types.ts +0 -0
  41. package/src/services/awards/award-callbacks.js +0 -0
  42. package/src/services/awards/award-query.js +0 -0
  43. package/src/services/awards/internal/.indexignore +0 -0
  44. package/src/services/awards/internal/award-definitions.js +0 -0
  45. package/src/services/awards/internal/award-events.js +0 -0
  46. package/src/services/awards/internal/award-manager.js +0 -0
  47. package/src/services/awards/internal/certificate-builder.js +0 -0
  48. package/src/services/awards/internal/completion-data-generator.js +0 -0
  49. package/src/services/awards/internal/content-progress-observer.js +0 -0
  50. package/src/services/awards/internal/image-utils.js +0 -0
  51. package/src/services/awards/internal/message-generator.js +0 -0
  52. package/src/services/awards/internal/types.js +0 -0
  53. package/src/services/awards/types.d.ts +0 -0
  54. package/src/services/awards/types.js +0 -0
  55. package/src/services/content/artist.ts +0 -0
  56. package/src/services/content/content.ts +0 -0
  57. package/src/services/content/genre.ts +0 -0
  58. package/src/services/content/instructor.ts +0 -0
  59. package/src/services/content-org/content-org.js +0 -0
  60. package/src/services/content-org/guided-courses.ts +0 -0
  61. package/src/services/content-org/learning-paths.ts +0 -0
  62. package/src/services/content-org/playlists-types.js +0 -0
  63. package/src/services/content-org/playlists.js +0 -0
  64. package/src/services/content.js +54 -15
  65. package/src/services/contentAggregator.js +0 -0
  66. package/src/services/contentLikes.js +0 -0
  67. package/src/services/contentProgress.js +0 -0
  68. package/src/services/dataContext.js +41 -0
  69. package/src/services/dateUtils.js +0 -0
  70. package/src/services/eventsAPI.js +0 -0
  71. package/src/services/forums/categories.ts +0 -0
  72. package/src/services/forums/forums.ts +0 -0
  73. package/src/services/forums/posts.ts +0 -0
  74. package/src/services/forums/threads.ts +0 -0
  75. package/src/services/forums/types.ts +0 -0
  76. package/src/services/gamification/awards.ts +0 -0
  77. package/src/services/gamification/gamification.js +0 -0
  78. package/src/services/imageSRCBuilder.js +0 -0
  79. package/src/services/imageSRCVerify.js +0 -0
  80. package/src/services/liveTesting.ts +0 -0
  81. package/src/services/permissions/PermissionsAdapter.ts +0 -0
  82. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  83. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  84. package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
  85. package/src/services/permissions/README.md +0 -0
  86. package/src/services/permissions/index.ts +0 -0
  87. package/src/services/progress-events.js +0 -0
  88. package/src/services/progress-row/base.js +13 -3
  89. package/src/services/progress-row/rows/.indexignore +0 -0
  90. package/src/services/progress-row/rows/content-card.js +0 -0
  91. package/src/services/progress-row/rows/playlist-card.js +0 -0
  92. package/src/services/railcontent.js +0 -0
  93. package/src/services/recommendations.js +5 -2
  94. package/src/services/reporting/README.md +0 -0
  95. package/src/services/reporting/reporting.ts +0 -0
  96. package/src/services/reporting/types.ts +0 -0
  97. package/src/services/sentry/.indexignore +0 -0
  98. package/src/services/sentry/index.ts +0 -0
  99. package/src/services/sync/.indexignore +0 -0
  100. package/src/services/sync/adapters/factory.ts +0 -0
  101. package/src/services/sync/adapters/lokijs.ts +0 -0
  102. package/src/services/sync/adapters/sqlite.ts +0 -0
  103. package/src/services/sync/context/index.ts +0 -0
  104. package/src/services/sync/context/providers/base.ts +0 -0
  105. package/src/services/sync/context/providers/connectivity.ts +0 -0
  106. package/src/services/sync/context/providers/durability.ts +0 -0
  107. package/src/services/sync/context/providers/index.ts +0 -0
  108. package/src/services/sync/context/providers/session.ts +0 -0
  109. package/src/services/sync/context/providers/tabs.ts +0 -0
  110. package/src/services/sync/context/providers/visibility.ts +0 -0
  111. package/src/services/sync/database/factory.ts +0 -0
  112. package/src/services/sync/effects/index.ts +0 -0
  113. package/src/services/sync/effects/logout-warning.ts +0 -0
  114. package/src/services/sync/errors/boundary.ts +0 -0
  115. package/src/services/sync/errors/index.ts +0 -0
  116. package/src/services/sync/errors/validators.ts +0 -0
  117. package/src/services/sync/fetch.ts +0 -0
  118. package/src/services/sync/index.ts +0 -0
  119. package/src/services/sync/manager.ts +0 -0
  120. package/src/services/sync/models/Base.ts +0 -0
  121. package/src/services/sync/models/ContentLike.ts +0 -0
  122. package/src/services/sync/models/ContentProgress.ts +0 -0
  123. package/src/services/sync/models/Practice.ts +0 -0
  124. package/src/services/sync/models/PracticeDayNote.ts +0 -0
  125. package/src/services/sync/models/UserAwardProgress.ts +0 -0
  126. package/src/services/sync/models/index.ts +0 -0
  127. package/src/services/sync/repositories/base.ts +0 -0
  128. package/src/services/sync/repositories/content-likes.ts +0 -0
  129. package/src/services/sync/repositories/content-progress.ts +0 -0
  130. package/src/services/sync/repositories/index.ts +0 -0
  131. package/src/services/sync/repositories/practice-day-notes.ts +0 -0
  132. package/src/services/sync/repositories/practices.ts +0 -0
  133. package/src/services/sync/repositories/user-award-progress.ts +0 -0
  134. package/src/services/sync/repository-proxy.ts +0 -0
  135. package/src/services/sync/resolver.ts +0 -0
  136. package/src/services/sync/retry.ts +0 -0
  137. package/src/services/sync/run-scope.ts +0 -0
  138. package/src/services/sync/schema/index.ts +0 -0
  139. package/src/services/sync/serializers/index.ts +0 -0
  140. package/src/services/sync/serializers/model.ts +0 -0
  141. package/src/services/sync/serializers/raw.ts +0 -0
  142. package/src/services/sync/store/index.ts +0 -0
  143. package/src/services/sync/store/push-coalescer.ts +0 -0
  144. package/src/services/sync/store-configs.ts +0 -0
  145. package/src/services/sync/strategies/base.ts +0 -0
  146. package/src/services/sync/strategies/index.ts +0 -0
  147. package/src/services/sync/strategies/initial.ts +0 -0
  148. package/src/services/sync/strategies/polling.ts +0 -0
  149. package/src/services/sync/telemetry/flood-prevention.ts +0 -0
  150. package/src/services/sync/telemetry/index.ts +0 -0
  151. package/src/services/sync/telemetry/sampling.ts +0 -0
  152. package/src/services/sync/utils/event-emitter.ts +0 -0
  153. package/src/services/sync/utils/index.ts +0 -0
  154. package/src/services/sync/utils/throttle.ts +0 -0
  155. package/src/services/sync/utils/timers.ts +0 -0
  156. package/src/services/urlBuilder.ts +0 -0
  157. package/src/services/user/account.ts +0 -0
  158. package/src/services/user/chat.js +0 -0
  159. package/src/services/user/interests.js +0 -0
  160. package/src/services/user/management.js +0 -0
  161. package/src/services/user/memberships.ts +0 -0
  162. package/src/services/user/notifications.js +0 -0
  163. package/src/services/user/onboarding.ts +0 -0
  164. package/src/services/user/payments.ts +0 -0
  165. package/src/services/user/permissions.js +0 -0
  166. package/src/services/user/profile.js +0 -0
  167. package/src/services/user/sessions.js +9 -2
  168. package/src/services/user/types.d.ts +0 -0
  169. package/src/services/user/types.js +0 -0
  170. package/src/services/user/user-management-system.js +0 -0
  171. package/src/services/userActivity.js +0 -0
  172. package/test/HttpClient.test.js +0 -0
  173. package/test/awards/award-alacarte-observer.test.js +0 -0
  174. package/test/awards/award-auto-refresh.test.js +0 -0
  175. package/test/awards/award-calculations.test.js +0 -0
  176. package/test/awards/award-certificate-display.test.js +0 -0
  177. package/test/awards/award-collection-edge-cases.test.js +0 -0
  178. package/test/awards/award-collection-filtering.test.js +0 -0
  179. package/test/awards/award-completion-flow.test.js +0 -0
  180. package/test/awards/award-exclusion-handling.test.js +0 -0
  181. package/test/awards/award-multi-lesson.test.js +0 -0
  182. package/test/awards/award-observer-integration.test.js +0 -0
  183. package/test/awards/award-query-messages.test.js +0 -0
  184. package/test/awards/award-user-collection.test.js +0 -0
  185. package/test/awards/duplicate-prevention.test.js +0 -0
  186. package/test/awards/helpers/completion-mock.js +0 -0
  187. package/test/awards/helpers/index.js +0 -0
  188. package/test/awards/helpers/mock-setup.js +0 -0
  189. package/test/awards/helpers/progress-emitter.js +0 -0
  190. package/test/awards/message-generator.test.js +0 -0
  191. package/test/content.test.js +0 -0
  192. package/test/contentLikes.test.js +0 -0
  193. package/test/contentProgress.test.js +0 -0
  194. package/test/dataContext.test.js +0 -0
  195. package/test/forum.test.js +0 -0
  196. package/test/imageSRCBuilder.test.js +0 -0
  197. package/test/imageSRCVerify.test.js +0 -0
  198. package/test/initializeTests.js +0 -0
  199. package/test/learningPaths.test.js +0 -0
  200. package/test/lib/__snapshots__/filter.test.ts.snap +0 -0
  201. package/test/lib/filter.test.ts +0 -0
  202. package/test/lib/lastUpdated.test.js +0 -0
  203. package/test/lib/query.test.ts +0 -0
  204. package/test/live/contentProgressLive.test.js +0 -0
  205. package/test/live/railcontentLive.test.js +0 -0
  206. package/test/log.js +0 -0
  207. package/test/logout.test.js +199 -0
  208. package/test/mockData/award-definitions.js +0 -0
  209. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  210. package/test/mockData/mockData_progress_content.json +0 -0
  211. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  212. package/test/mockData/mockData_user_practices.json +0 -0
  213. package/test/notifications.test.js +0 -0
  214. package/test/progressRows.test.js +0 -0
  215. package/test/reporting.test.js +132 -0
  216. package/test/sanityQueryService.test.js +0 -0
  217. package/test/streakMessage.test.js +0 -0
  218. package/test/sync/adapter.ts +0 -0
  219. package/test/sync/initialize-sync-manager.js +0 -0
  220. package/test/sync/models/award-database-integration.test.js +0 -0
  221. package/test/user/permissions.test.js +0 -0
  222. package/test/userActivity.test.js +0 -0
  223. package/test_owned_navigate.js +74 -0
  224. package/tools/generate-index.cjs +0 -0
  225. package/tsconfig.json +17 -0
@@ -126,9 +126,12 @@ export async function rankItems(brand, content_ids) {
126
126
  }
127
127
  }
128
128
 
129
- export async function recommendations(brand, { section = '' } = {}) {
129
+ export async function recommendations(brand, { section = '', contentTypes = [] } = {}) {
130
130
  section = section.toUpperCase().replace('-', '_')
131
131
  const sectionString = section ? `&section=${section}` : ''
132
- const url = `/api/content/v1/recommendations?brand=${brand}${sectionString}`
132
+ const contentTypesString = contentTypes.length > 0
133
+ ? contentTypes.map(type => `&content_types[]=${encodeURIComponent(type)}`).join('')
134
+ : ''
135
+ const url = `/api/content/v1/recommendations?brand=${brand}${sectionString}${contentTypesString}`
133
136
  return await GET(url)
134
137
  }
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
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
@@ -2,6 +2,7 @@
2
2
  * @module Sessions
3
3
  */
4
4
  import { globalConfig } from '../config.js'
5
+ import { clearAllCachedData } from '../dataContext.js'
5
6
  import { USER_PIN_PROGRESS_KEY } from '../progress-row/base.js'
6
7
  import './types.js'
7
8
 
@@ -50,9 +51,11 @@ export async function login(email, password, deviceName, deviceToken, platform)
50
51
 
51
52
  // TODO: refactor this. I don't think this is the place for it but we need it fixed for the system test
52
53
  if (res.ok) {
54
+ const userId = data.user?.id
55
+ const userPinKey = userId ? `user_pin_progress_row_${userId}` : USER_PIN_PROGRESS_KEY
53
56
  await globalConfig.localStorage.setItem(
54
- USER_PIN_PROGRESS_KEY,
55
- JSON.stringify(data.pinned_progress_rows || {})
57
+ userPinKey,
58
+ JSON.stringify(data.user?.brand_pinned_progress || {})
56
59
  )
57
60
  }
58
61
 
@@ -91,6 +94,7 @@ export async function login(email, password, deviceName, deviceToken, platform)
91
94
 
92
95
  /**
93
96
  * Logs the user out of the current session.
97
+ * Clears all cached data to prevent data leakage between users.
94
98
  *
95
99
  * @returns {Promise<void>}
96
100
  *
@@ -108,6 +112,9 @@ export async function logout() {
108
112
  'Content-Type': 'application/json',
109
113
  },
110
114
  })
115
+
116
+ // Clear all locally cached data to prevent data leakage between users
117
+ await clearAllCachedData()
111
118
  }
112
119
 
113
120
  /**
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
package/test/log.js CHANGED
File without changes
@@ -0,0 +1,199 @@
1
+ import { initializeService, globalConfig } from '../src/services/config.js'
2
+ import { LocalStorageMock } from './localStorageMock'
3
+ import { logout } from '../src/services/user/sessions.js'
4
+ import { clearAllCachedData } from '../src/services/dataContext.js'
5
+ import { USER_PIN_PROGRESS_KEY } from '../src/services/progress-row/base.js'
6
+
7
+ describe('logout cache clearing', function () {
8
+ let mockLocalStorage
9
+
10
+ beforeEach(() => {
11
+ // Create fresh localStorage for each test
12
+ mockLocalStorage = new LocalStorageMock()
13
+
14
+ // Simple initialization without SyncManager
15
+ const config = {
16
+ sanityConfig: {
17
+ token: 'test-token',
18
+ projectId: 'test-project',
19
+ dataset: 'test',
20
+ useCachedAPI: true,
21
+ version: '2021-06-07',
22
+ debug: false,
23
+ useDummyRailContentMethods: true,
24
+ },
25
+ railcontentConfig: {
26
+ baseUrl: 'https://test.musora.com',
27
+ token: 'test-token',
28
+ userId: 123,
29
+ authToken: 'test-token',
30
+ },
31
+ sessionConfig: { token: 'test-token', userId: 123, authToken: 'test-token' },
32
+ baseUrl: 'https://test.musora.com',
33
+ localStorage: mockLocalStorage,
34
+ isMA: false,
35
+ }
36
+ initializeService(config)
37
+ })
38
+
39
+ afterEach(() => {
40
+ jest.restoreAllMocks()
41
+ })
42
+
43
+ test('clearAllCachedData does NOT remove user_pin_progress_row keys', async () => {
44
+ const localStorage = globalConfig.localStorage
45
+ const userId = 123
46
+ const userPinKey = `user_pin_progress_row_${userId}`
47
+
48
+ // Add pinned progress data for user 123
49
+ localStorage.setItem(userPinKey, JSON.stringify({
50
+ drumeo: { id: 123, progressType: 'content', pinnedAt: '2025-01-01' },
51
+ pianote: { id: 456, progressType: 'playlist', pinnedAt: '2025-01-02' }
52
+ }))
53
+
54
+ // Add pinned progress data for another user
55
+ localStorage.setItem('user_pin_progress_row_456', JSON.stringify({
56
+ drumeo: { id: 789, progressType: 'content' }
57
+ }))
58
+
59
+ // Verify data exists
60
+ expect(localStorage.getItem(userPinKey)).not.toBeNull()
61
+ expect(localStorage.getItem('user_pin_progress_row_456')).not.toBeNull()
62
+
63
+ // Clear cached data
64
+ await clearAllCachedData()
65
+
66
+ // Verify user pin progress rows are NOT removed
67
+ expect(localStorage.getItem(userPinKey)).not.toBeNull()
68
+ expect(localStorage.getItem('user_pin_progress_row_456')).not.toBeNull()
69
+ })
70
+
71
+ test('clearAllCachedData removes dataContext keys', async () => {
72
+ const localStorage = globalConfig.localStorage
73
+
74
+ // Add some dataContext keys
75
+ localStorage.setItem('dataContext_test1', JSON.stringify({ data: 'test1' }))
76
+ localStorage.setItem('dataContext_test2', JSON.stringify({ data: 'test2' }))
77
+ localStorage.setItem('other_key', 'should_remain')
78
+
79
+ // Verify data exists
80
+ expect(localStorage.getItem('dataContext_test1')).not.toBeNull()
81
+ expect(localStorage.getItem('dataContext_test2')).not.toBeNull()
82
+ expect(localStorage.getItem('other_key')).not.toBeNull()
83
+
84
+ // Clear all cached data
85
+ await clearAllCachedData()
86
+
87
+ // Verify dataContext keys were removed but other key remains
88
+ expect(localStorage.getItem('dataContext_test1')).toBeNull()
89
+ expect(localStorage.getItem('dataContext_test2')).toBeNull()
90
+ expect(localStorage.getItem('other_key')).toBe('should_remain')
91
+ })
92
+
93
+ test('clearAllCachedData removes ONLY dataContext keys', async () => {
94
+ const localStorage = globalConfig.localStorage
95
+ const userId = 123
96
+ const userPinKey = `user_pin_progress_row_${userId}`
97
+
98
+ // Add various cached data
99
+ localStorage.setItem(userPinKey, JSON.stringify({ drumeo: { id: 123 } }))
100
+ localStorage.setItem('dataContext_progress', JSON.stringify({ data: 'progress' }))
101
+ localStorage.setItem('dataContext_likes', JSON.stringify({ data: 'likes' }))
102
+ localStorage.setItem('unrelated_key', 'preserved')
103
+ localStorage.setItem('user_pin_progress_row_999', JSON.stringify({ drumeo: { id: 999 } }))
104
+
105
+ // Verify all data exists
106
+ expect(localStorage.getItem(userPinKey)).not.toBeNull()
107
+ expect(localStorage.getItem('dataContext_progress')).not.toBeNull()
108
+ expect(localStorage.getItem('dataContext_likes')).not.toBeNull()
109
+ expect(localStorage.getItem('unrelated_key')).not.toBeNull()
110
+
111
+ // Clear cached data
112
+ await clearAllCachedData()
113
+
114
+ // Verify only dataContext keys were cleared
115
+ expect(localStorage.getItem(userPinKey)).not.toBeNull()
116
+ expect(localStorage.getItem('dataContext_progress')).toBeNull()
117
+ expect(localStorage.getItem('dataContext_likes')).toBeNull()
118
+ expect(localStorage.getItem('unrelated_key')).toBe('preserved')
119
+ // All user pinned data should remain
120
+ expect(localStorage.getItem('user_pin_progress_row_999')).not.toBeNull()
121
+ })
122
+
123
+ test('logout calls clearAllCachedData', async () => {
124
+ // Mock fetch to prevent actual API call
125
+ global.fetch = jest.fn().mockResolvedValue({
126
+ ok: true,
127
+ json: async () => ({ success: true })
128
+ })
129
+
130
+ const localStorage = globalConfig.localStorage
131
+ const userId = 123
132
+ const userPinKey = `user_pin_progress_row_${userId}`
133
+
134
+ // Add cached data
135
+ localStorage.setItem(userPinKey, JSON.stringify({ drumeo: { id: 123 } }))
136
+ localStorage.setItem('dataContext_test', JSON.stringify({ data: 'test' }))
137
+ localStorage.setItem('user_pin_progress_row_456', JSON.stringify({ drumeo: { id: 456 } }))
138
+
139
+ // Verify data exists before logout
140
+ expect(localStorage.getItem(userPinKey)).not.toBeNull()
141
+ expect(localStorage.getItem('dataContext_test')).not.toBeNull()
142
+
143
+ // Call logout
144
+ await logout()
145
+
146
+ // Verify only dataContext was cleared, pinned progress remains
147
+ expect(localStorage.getItem(userPinKey)).not.toBeNull()
148
+ expect(localStorage.getItem('dataContext_test')).toBeNull()
149
+ expect(localStorage.getItem('user_pin_progress_row_456')).not.toBeNull()
150
+
151
+ // Verify fetch was called (logout API endpoint)
152
+ expect(global.fetch).toHaveBeenCalledWith(
153
+ expect.stringContaining('/api/user-management-system/v1/sessions'),
154
+ expect.objectContaining({
155
+ method: 'DELETE'
156
+ })
157
+ )
158
+ })
159
+
160
+ test('logout prevents cross-account data leakage', async () => {
161
+ // Mock fetch to prevent actual API call
162
+ global.fetch = jest.fn().mockResolvedValue({
163
+ ok: true,
164
+ json: async () => ({ success: true })
165
+ })
166
+
167
+ const localStorage = globalConfig.localStorage
168
+ const user1Id = 123
169
+ const user2Id = 456
170
+ const user1PinKey = `user_pin_progress_row_${user1Id}`
171
+ const user2PinKey = `user_pin_progress_row_${user2Id}`
172
+
173
+ // Simulate user 1 (id: 123) with pinned content and dataContext
174
+ localStorage.setItem(user1PinKey, JSON.stringify({
175
+ drumeo: { id: 12345, progressType: 'content', pinnedAt: '2025-01-14' }
176
+ }))
177
+ localStorage.setItem('dataContext_progress', JSON.stringify({ userId: 1, data: 'user1' }))
178
+
179
+ // Simulate user 2 (id: 456) already has pinned content
180
+ localStorage.setItem(user2PinKey, JSON.stringify({
181
+ drumeo: { id: 67890, progressType: 'content', pinnedAt: '2025-01-13' }
182
+ }))
183
+
184
+ // User 1 logs out
185
+ await logout()
186
+
187
+ // Verify dataContext is cleared (prevents cross-account leakage)
188
+ expect(localStorage.getItem('dataContext_progress')).toBeNull()
189
+
190
+ // Verify user 1's pinned progress remains (user-specific key)
191
+ expect(localStorage.getItem(user1PinKey)).not.toBeNull()
192
+
193
+ // Verify user 2's pinned content is NOT affected by user 1's logout
194
+ const user2PinnedData = localStorage.getItem(user2PinKey)
195
+ expect(user2PinnedData).not.toBeNull()
196
+ const user2Data = JSON.parse(user2PinnedData)
197
+ expect(user2Data.drumeo.id).toBe(67890)
198
+ })
199
+ })
File without changes
File without changes
File without changes
File without changes
File without changes