@studious-lms/server 1.2.45 → 1.2.47

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 (241) hide show
  1. package/.env.example +45 -0
  2. package/.env.test.example +37 -0
  3. package/README.md +34 -7
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/clover.xml +12110 -0
  7. package/coverage/coverage-final.json +44 -0
  8. package/coverage/favicon.png +0 -0
  9. package/coverage/index.html +221 -0
  10. package/coverage/prettify.css +1 -0
  11. package/coverage/prettify.js +2 -0
  12. package/coverage/server/index.html +116 -0
  13. package/coverage/server/src/exportType.ts.html +109 -0
  14. package/coverage/server/src/index.html +161 -0
  15. package/coverage/server/src/index.ts.html +1702 -0
  16. package/coverage/server/src/instrument.ts.html +130 -0
  17. package/coverage/server/src/lib/config/env.ts.html +448 -0
  18. package/coverage/server/src/lib/config/index.html +116 -0
  19. package/coverage/server/src/lib/fileUpload.ts.html +1138 -0
  20. package/coverage/server/src/lib/googleCloudStorage.ts.html +334 -0
  21. package/coverage/server/src/lib/index.html +206 -0
  22. package/coverage/server/src/lib/jsonConversion.ts.html +2323 -0
  23. package/coverage/server/src/lib/jsonStyles.ts.html +193 -0
  24. package/coverage/server/src/lib/notificationHandler.ts.html +193 -0
  25. package/coverage/server/src/lib/pusher.ts.html +121 -0
  26. package/coverage/server/src/lib/thumbnailGenerator.ts.html +592 -0
  27. package/coverage/server/src/middleware/auth.ts.html +646 -0
  28. package/coverage/server/src/middleware/index.html +146 -0
  29. package/coverage/server/src/middleware/logging.ts.html +244 -0
  30. package/coverage/server/src/middleware/security.ts.html +271 -0
  31. package/coverage/server/src/routers/_app.ts.html +232 -0
  32. package/coverage/server/src/routers/agenda.ts.html +319 -0
  33. package/coverage/server/src/routers/announcement.ts.html +3481 -0
  34. package/coverage/server/src/routers/assignment.ts.html +7633 -0
  35. package/coverage/server/src/routers/attendance.ts.html +1030 -0
  36. package/coverage/server/src/routers/auth.ts.html +1081 -0
  37. package/coverage/server/src/routers/class.ts.html +3535 -0
  38. package/coverage/server/src/routers/comment.ts.html +991 -0
  39. package/coverage/server/src/routers/conversation.ts.html +982 -0
  40. package/coverage/server/src/routers/event.ts.html +1609 -0
  41. package/coverage/server/src/routers/file.ts.html +1144 -0
  42. package/coverage/server/src/routers/folder.ts.html +2797 -0
  43. package/coverage/server/src/routers/index.html +386 -0
  44. package/coverage/server/src/routers/labChat.ts.html +3073 -0
  45. package/coverage/server/src/routers/marketing.ts.html +340 -0
  46. package/coverage/server/src/routers/message.ts.html +1912 -0
  47. package/coverage/server/src/routers/notifications.ts.html +364 -0
  48. package/coverage/server/src/routers/section.ts.html +1120 -0
  49. package/coverage/server/src/routers/user.ts.html +862 -0
  50. package/coverage/server/src/routers/worksheet.ts.html +1729 -0
  51. package/coverage/server/src/trpc.ts.html +397 -0
  52. package/coverage/server/src/types/index.html +116 -0
  53. package/coverage/server/src/types/trpc.ts.html +127 -0
  54. package/coverage/server/src/utils/aiUser.ts.html +280 -0
  55. package/coverage/server/src/utils/email.ts.html +121 -0
  56. package/coverage/server/src/utils/generateInviteCode.ts.html +106 -0
  57. package/coverage/server/src/utils/index.html +206 -0
  58. package/coverage/server/src/utils/inference.ts.html +709 -0
  59. package/coverage/server/src/utils/logger.ts.html +664 -0
  60. package/coverage/server/src/utils/prismaErrorHandler.ts.html +907 -0
  61. package/coverage/server/src/utils/prismaWrapper.ts.html +355 -0
  62. package/coverage/server/vitest.config.ts.html +196 -0
  63. package/coverage/sort-arrow-sprite.png +0 -0
  64. package/coverage/sorter.js +210 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +83 -52
  67. package/dist/index.js.map +1 -1
  68. package/dist/instrument.js +15 -8
  69. package/dist/instrument.js.map +1 -1
  70. package/dist/lib/config/env.d.ts +169 -0
  71. package/dist/lib/config/env.d.ts.map +1 -0
  72. package/dist/lib/config/env.js +115 -0
  73. package/dist/lib/config/env.js.map +1 -0
  74. package/dist/lib/fileUpload.d.ts.map +1 -1
  75. package/dist/lib/fileUpload.js +5 -4
  76. package/dist/lib/fileUpload.js.map +1 -1
  77. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  78. package/dist/lib/googleCloudStorage.js +7 -8
  79. package/dist/lib/googleCloudStorage.js.map +1 -1
  80. package/dist/lib/jsonConversion.d.ts.map +1 -1
  81. package/dist/lib/jsonConversion.js +14 -16
  82. package/dist/lib/jsonConversion.js.map +1 -1
  83. package/dist/lib/notificationHandler.d.ts +2 -2
  84. package/dist/lib/prisma.d.ts +2 -2
  85. package/dist/lib/prisma.d.ts.map +1 -1
  86. package/dist/lib/prisma.js +22 -3
  87. package/dist/lib/prisma.js.map +1 -1
  88. package/dist/lib/pusher.d.ts.map +1 -1
  89. package/dist/lib/pusher.js +8 -7
  90. package/dist/lib/pusher.js.map +1 -1
  91. package/dist/middleware/auth.d.ts.map +1 -1
  92. package/dist/middleware/auth.js +7 -5
  93. package/dist/middleware/auth.js.map +1 -1
  94. package/dist/middleware/security.d.ts +5 -0
  95. package/dist/middleware/security.d.ts.map +1 -0
  96. package/dist/middleware/security.js +77 -0
  97. package/dist/middleware/security.js.map +1 -0
  98. package/dist/routers/_app.d.ts +368 -108
  99. package/dist/routers/_app.d.ts.map +1 -1
  100. package/dist/routers/_app.js +4 -2
  101. package/dist/routers/_app.js.map +1 -1
  102. package/dist/routers/agenda.d.ts.map +1 -1
  103. package/dist/routers/agenda.js +12 -9
  104. package/dist/routers/agenda.js.map +1 -1
  105. package/dist/routers/announcement.d.ts +8 -0
  106. package/dist/routers/announcement.d.ts.map +1 -1
  107. package/dist/routers/announcement.js +6 -4
  108. package/dist/routers/announcement.js.map +1 -1
  109. package/dist/routers/assignment.d.ts +17 -4
  110. package/dist/routers/assignment.d.ts.map +1 -1
  111. package/dist/routers/assignment.js +51 -19
  112. package/dist/routers/assignment.js.map +1 -1
  113. package/dist/routers/attendance.d.ts +1 -0
  114. package/dist/routers/attendance.d.ts.map +1 -1
  115. package/dist/routers/attendance.js +4 -4
  116. package/dist/routers/attendance.js.map +1 -1
  117. package/dist/routers/auth.d.ts +20 -0
  118. package/dist/routers/auth.d.ts.map +1 -1
  119. package/dist/routers/auth.js +132 -15
  120. package/dist/routers/auth.js.map +1 -1
  121. package/dist/routers/class.d.ts +10 -0
  122. package/dist/routers/class.d.ts.map +1 -1
  123. package/dist/routers/class.js +49 -5
  124. package/dist/routers/class.js.map +1 -1
  125. package/dist/routers/comment.d.ts +2 -0
  126. package/dist/routers/comment.d.ts.map +1 -1
  127. package/dist/routers/conversation.d.ts +2 -0
  128. package/dist/routers/conversation.d.ts.map +1 -1
  129. package/dist/routers/conversation.js +46 -31
  130. package/dist/routers/conversation.js.map +1 -1
  131. package/dist/routers/file.d.ts.map +1 -1
  132. package/dist/routers/file.js +30 -7
  133. package/dist/routers/file.js.map +1 -1
  134. package/dist/routers/labChat.d.ts +2 -0
  135. package/dist/routers/labChat.d.ts.map +1 -1
  136. package/dist/routers/labChat.js +5 -322
  137. package/dist/routers/labChat.js.map +1 -1
  138. package/dist/routers/marketing.d.ts +1 -1
  139. package/dist/routers/message.d.ts +1 -0
  140. package/dist/routers/message.d.ts.map +1 -1
  141. package/dist/routers/message.js +3 -2
  142. package/dist/routers/message.js.map +1 -1
  143. package/dist/routers/newtonChat.d.ts +55 -0
  144. package/dist/routers/newtonChat.d.ts.map +1 -0
  145. package/dist/routers/newtonChat.js +262 -0
  146. package/dist/routers/newtonChat.js.map +1 -0
  147. package/dist/routers/notifications.d.ts +4 -4
  148. package/dist/routers/section.d.ts +19 -4
  149. package/dist/routers/section.d.ts.map +1 -1
  150. package/dist/routers/section.js +26 -8
  151. package/dist/routers/section.js.map +1 -1
  152. package/dist/routers/user.d.ts.map +1 -1
  153. package/dist/routers/user.js +5 -4
  154. package/dist/routers/user.js.map +1 -1
  155. package/dist/routers/worksheet.d.ts +44 -41
  156. package/dist/routers/worksheet.d.ts.map +1 -1
  157. package/dist/routers/worksheet.js +25 -34
  158. package/dist/routers/worksheet.js.map +1 -1
  159. package/dist/seedDatabase.d.ts +1 -1
  160. package/dist/seedDatabase.js +275 -284
  161. package/dist/seedDatabase.js.map +1 -1
  162. package/dist/server/pipelines/aiLabChat.d.ts +21 -0
  163. package/dist/server/pipelines/aiLabChat.d.ts.map +1 -0
  164. package/dist/server/pipelines/aiLabChat.js +456 -0
  165. package/dist/server/pipelines/aiLabChat.js.map +1 -0
  166. package/dist/server/pipelines/aiNewtonChat.d.ts +30 -0
  167. package/dist/server/pipelines/aiNewtonChat.d.ts.map +1 -0
  168. package/dist/server/pipelines/aiNewtonChat.js +280 -0
  169. package/dist/server/pipelines/aiNewtonChat.js.map +1 -0
  170. package/dist/server/pipelines/gradeWorksheet.d.ts +15 -0
  171. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -0
  172. package/dist/server/pipelines/gradeWorksheet.js +139 -0
  173. package/dist/server/pipelines/gradeWorksheet.js.map +1 -0
  174. package/dist/trpc.d.ts.map +1 -1
  175. package/dist/trpc.js +2 -2
  176. package/dist/trpc.js.map +1 -1
  177. package/dist/utils/email.d.ts +9 -1
  178. package/dist/utils/email.d.ts.map +1 -1
  179. package/dist/utils/email.js +20 -5
  180. package/dist/utils/email.js.map +1 -1
  181. package/dist/utils/inference.d.ts +5 -0
  182. package/dist/utils/inference.d.ts.map +1 -1
  183. package/dist/utils/inference.js +71 -7
  184. package/dist/utils/inference.js.map +1 -1
  185. package/dist/utils/logger.d.ts.map +1 -1
  186. package/dist/utils/logger.js +3 -3
  187. package/dist/utils/logger.js.map +1 -1
  188. package/docker-compose.yml +14 -0
  189. package/package.json +13 -4
  190. package/prisma/schema.prisma +34 -5
  191. package/scripts/test-pre-push.ts +14 -0
  192. package/src/index.ts +98 -54
  193. package/src/instrument.ts +13 -6
  194. package/src/lib/config/env.ts +126 -0
  195. package/src/lib/fileUpload.ts +3 -2
  196. package/src/lib/googleCloudStorage.ts +6 -6
  197. package/src/lib/jsonConversion.ts +12 -14
  198. package/src/lib/prisma.ts +23 -2
  199. package/src/lib/pusher.ts +6 -5
  200. package/src/middleware/auth.ts +5 -3
  201. package/src/middleware/security.ts +80 -0
  202. package/src/routers/_app.ts +2 -0
  203. package/src/routers/agenda.ts +10 -7
  204. package/src/routers/announcement.ts +4 -2
  205. package/src/routers/assignment.ts +74 -41
  206. package/src/routers/attendance.ts +2 -2
  207. package/src/routers/auth.ts +143 -14
  208. package/src/routers/class.ts +52 -3
  209. package/src/routers/conversation.ts +49 -29
  210. package/src/routers/file.ts +29 -5
  211. package/src/routers/labChat.ts +3 -367
  212. package/src/routers/message.ts +1 -1
  213. package/src/routers/newtonChat.ts +299 -0
  214. package/src/routers/section.ts +26 -6
  215. package/src/routers/user.ts +3 -2
  216. package/src/routers/worksheet.ts +26 -38
  217. package/src/seedDatabase.ts +290 -283
  218. package/src/server/pipelines/aiLabChat.ts +507 -0
  219. package/src/server/pipelines/aiNewtonChat.ts +338 -0
  220. package/src/server/pipelines/gradeWorksheet.ts +151 -0
  221. package/src/trpc.ts +2 -0
  222. package/src/utils/email.ts +30 -3
  223. package/src/utils/inference.ts +85 -5
  224. package/src/utils/logger.ts +2 -1
  225. package/tests/announcement.test.ts +164 -0
  226. package/tests/assignment.test.ts +296 -0
  227. package/tests/attendance.test.ts +168 -0
  228. package/tests/auth.test.ts +33 -10
  229. package/tests/class.test.ts +34 -9
  230. package/tests/event.test.ts +228 -0
  231. package/tests/section.test.ts +216 -0
  232. package/tests/setup.ts +70 -16
  233. package/tests/user.test.ts +158 -0
  234. package/vitest.config.ts +26 -0
  235. package/API_SPECIFICATION.md +0 -1597
  236. package/BASE64_REMOVAL_SUMMARY.md +0 -164
  237. package/CHAT_API_SPEC.md +0 -579
  238. package/LAB_CHAT_API_SPEC.md +0 -518
  239. package/dist/routers/school.d.ts +0 -208
  240. package/dist/routers/school.d.ts.map +0 -1
  241. package/dist/routers/school.js +0 -483
package/tests/setup.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { execSync } from 'child_process';
2
+ import { config } from 'dotenv';
3
+ import { resolve } from 'path';
2
4
  import { prisma } from '../src/lib/prisma';
3
5
  import { beforeAll, beforeEach, afterAll, afterEach } from 'vitest';
4
6
  import { logger } from '../src/utils/logger';
@@ -7,26 +9,31 @@ import { createTRPCContext } from '../src/trpc';
7
9
  import { Session } from '@prisma/client';
8
10
  import { clearDatabase } from '../src/seedDatabase';
9
11
 
12
+ // Load test environment variables
13
+ config({ path: resolve(process.cwd(), '.env.test') });
14
+
10
15
  const getCaller = async (token: string) => {
11
16
  const ctx = await createTRPCContext({
12
17
  req: { headers: {
13
- authorization: `Bearer ${token}`,
14
- 'x-user': token,
18
+ authorization: token ? `Bearer ${token}` : undefined,
19
+ 'x-user': token || undefined,
15
20
  } } as any,
16
21
  res: {} as any,
17
22
  });
18
23
  return appRouter.createCaller(ctx);
19
24
  };
20
25
 
21
- // Before the entire test suite runs
22
- beforeAll(async () => {
23
-
26
+ // Before the entire test suite runs
27
+ beforeAll(async () => {
28
+ try {
29
+ logger.info('DATABASE_URL: ' + process.env.DATABASE_URL);
24
30
  await clearDatabase();
31
+ logger.info('Database cleared for tests');
25
32
 
26
- logger.info('Getting caller');
27
-
33
+ // Create public caller (no auth) for registration
28
34
  caller = await getCaller('');
29
35
 
36
+ // Register users
30
37
  const user1 = await caller.auth.register({
31
38
  username: 'testuser1',
32
39
  email: 'test@test.com',
@@ -41,18 +48,37 @@ const getCaller = async (token: string) => {
41
48
  confirmPassword: 'password_is_1234',
42
49
  });
43
50
 
51
+ const user3 = await caller.auth.register({
52
+ username: 'testuser3',
53
+ email: 'test3@test.com',
54
+ password: 'password_is_1234',
55
+ confirmPassword: 'password_is_1234',
56
+ });
57
+
58
+ // Get sessions created during registration
44
59
  session1 = await prisma.session.findFirst({
45
60
  where: {
46
61
  userId: user1.user.id,
47
62
  },
48
63
  }) as Session;
49
64
 
50
- session2 = (await prisma.session.findFirst({
65
+ session2 = await prisma.session.findFirst({
51
66
  where: {
52
67
  userId: user2.user.id,
53
68
  },
54
- })) as Session;
69
+ }) as Session;
70
+
71
+ session3 = await prisma.session.findFirst({
72
+ where: {
73
+ userId: user3.user.id,
74
+ },
75
+ }) as Session;
55
76
 
77
+ if (!session1 || !session2 || !session3) {
78
+ throw new Error('Failed to create sessions for test users');
79
+ }
80
+
81
+ // Verify emails using session tokens
56
82
  verification1 = await caller.auth.verify({
57
83
  token: session1.id,
58
84
  });
@@ -61,6 +87,11 @@ const getCaller = async (token: string) => {
61
87
  token: session2.id,
62
88
  });
63
89
 
90
+ verification3 = await caller.auth.verify({
91
+ token: session3.id,
92
+ });
93
+
94
+ // Login to get fresh tokens
64
95
  login1 = await caller.auth.login({
65
96
  username: 'testuser1',
66
97
  password: 'password_is_1234',
@@ -71,22 +102,45 @@ const getCaller = async (token: string) => {
71
102
  password: 'password_is_1234',
72
103
  });
73
104
 
74
- user1Caller = await getCaller(login1.token);
75
- user2Caller = await getCaller(login2.token);
76
- });
105
+ login3 = await caller.auth.login({
106
+ username: 'testuser3',
107
+ password: 'password_is_1234',
108
+ });
77
109
 
110
+ if (!login1.token || !login2.token || !login3.token) {
111
+ throw new Error('Failed to get login tokens');
112
+ }
78
113
 
79
- // After all tests, close the DB
80
- afterAll(async () => {
114
+ // Create authenticated callers
115
+ user1Caller = await getCaller(login1.token);
116
+ user2Caller = await getCaller(login2.token);
117
+ user3Caller = await getCaller(login3.token);
118
+ logger.info('Test setup completed successfully');
119
+ } catch (error) {
120
+ logger.error('Test setup failed', { error });
121
+ throw error;
122
+ }
123
+ });
124
+
125
+ // After all tests, close the DB
126
+ afterAll(async () => {
127
+ try {
81
128
  await prisma.$disconnect();
82
- });
129
+ } catch (error) {
130
+ logger.error('Error disconnecting from database', { error });
131
+ }
132
+ });
83
133
 
84
134
  export let user1Caller: ReturnType<typeof appRouter.createCaller>;
85
135
  export let user2Caller: ReturnType<typeof appRouter.createCaller>;
136
+ export let user3Caller: ReturnType<typeof appRouter.createCaller>;
86
137
  export let caller: ReturnType<typeof appRouter.createCaller>;
87
138
  export let session1: Session;
88
139
  export let session2: Session;
140
+ export let session3: Session;
89
141
  export let verification1: any;
90
142
  export let verification2: any;
143
+ export let verification3: any;
91
144
  export let login1: any;
92
- export let login2: any;
145
+ export let login2: any;
146
+ export let login3: any;
@@ -0,0 +1,158 @@
1
+ import { test, expect, describe, beforeEach } from 'vitest';
2
+ import { user1Caller, user2Caller } from './setup';
3
+ import { createTRPCContext } from '../src/trpc';
4
+ import { appRouter } from '../src/routers/_app';
5
+
6
+ describe('User Router', () => {
7
+ describe('getProfile', () => {
8
+ test('should get user profile successfully', async () => {
9
+ const profile = await user1Caller.user.getProfile();
10
+
11
+ expect(profile).toBeDefined();
12
+ expect(profile.id).toBeDefined();
13
+ expect(profile.username).toBe('testuser1');
14
+ expect(profile.profile).toBeDefined();
15
+ expect(profile.profile.displayName).toBeNull();
16
+ expect(profile.profile.bio).toBeNull();
17
+ expect(profile.profile.location).toBeNull();
18
+ expect(profile.profile.website).toBeNull();
19
+ expect(profile.profile.profilePicture).toBeNull();
20
+ });
21
+
22
+ test('should fail without authentication', async () => {
23
+ const invalidCaller = await createTRPCContext({
24
+ req: { headers: {} } as any,
25
+ res: {} as any,
26
+ });
27
+ const router = appRouter.createCaller(invalidCaller);
28
+
29
+ await expect(router.user.getProfile()).rejects.toThrow();
30
+ });
31
+ });
32
+
33
+ describe('updateProfile', () => {
34
+ test('should update profile with display name and bio', async () => {
35
+ const updated = await user1Caller.user.updateProfile({
36
+ profile: {
37
+ displayName: 'Test User One',
38
+ bio: 'This is a test bio',
39
+ location: 'Test City',
40
+ website: 'https://example.com',
41
+ },
42
+ });
43
+
44
+ expect(updated).toBeDefined();
45
+ expect(updated.profile.displayName).toBe('Test User One');
46
+ expect(updated.profile.bio).toBe('This is a test bio');
47
+ expect(updated.profile.location).toBe('Test City');
48
+ expect(updated.profile.website).toBe('https://example.com');
49
+ });
50
+
51
+ test('should update profile with partial data', async () => {
52
+ const updated = await user2Caller.user.updateProfile({
53
+ profile: {
54
+ displayName: 'Test User Two',
55
+ },
56
+ });
57
+
58
+ expect(updated.profile.displayName).toBe('Test User Two');
59
+ });
60
+
61
+ test('should update profile with DiceBear avatar', async () => {
62
+ const updated = await user1Caller.user.updateProfile({
63
+ dicebearAvatar: {
64
+ url: 'https://api.dicebear.com/7.x/avataaars/svg?seed=test',
65
+ },
66
+ });
67
+
68
+ expect(updated.profile.profilePicture).toBe('https://api.dicebear.com/7.x/avataaars/svg?seed=test');
69
+ });
70
+
71
+ // test('should clear profile fields when set to null', async () => {
72
+ // // First set some values
73
+ // await user1Caller.user.updateProfile({
74
+ // profile: {
75
+ // displayName: 'Test Name',
76
+ // bio: 'Test Bio',
77
+ // },
78
+ // });
79
+
80
+ // // Then clear them
81
+ // const cleared = await user1Caller.user.updateProfile({
82
+ // profile: {
83
+ // displayName: null,
84
+ // bio: null,
85
+ // },
86
+ // });
87
+
88
+ // expect(cleared.profile.displayName).toBeNull();
89
+ // expect(cleared.profile.bio).toBeNull();
90
+ // });
91
+
92
+ test('should fail without authentication', async () => {
93
+ const invalidCaller = await createTRPCContext({
94
+ req: { headers: {} } as any,
95
+ res: {} as any,
96
+ });
97
+ const router = appRouter.createCaller(invalidCaller);
98
+
99
+ await expect(router.user.updateProfile({
100
+ profile: { displayName: 'Test' },
101
+ })).rejects.toThrow();
102
+ });
103
+ });
104
+
105
+ describe('getUploadUrl', () => {
106
+ test('should generate upload URL for valid image file', async () => {
107
+ const result = await user1Caller.user.getUploadUrl({
108
+ fileName: 'profile.jpg',
109
+ fileType: 'image/jpeg',
110
+ });
111
+
112
+ expect(result).toBeDefined();
113
+ expect(result.uploadUrl).toBeDefined();
114
+ expect(result.filePath).toBeDefined();
115
+ expect(result.fileName).toBeDefined();
116
+ expect(result.filePath).toContain('users/');
117
+ expect(result.filePath).toContain('/profile/');
118
+ });
119
+
120
+ test('should generate upload URL for PNG file', async () => {
121
+ const result = await user1Caller.user.getUploadUrl({
122
+ fileName: 'avatar.png',
123
+ fileType: 'image/png',
124
+ });
125
+
126
+ expect(result).toBeDefined();
127
+ expect(result.fileName).toContain('.png');
128
+ });
129
+
130
+ test('should fail for invalid file type', async () => {
131
+ await expect(user1Caller.user.getUploadUrl({
132
+ fileName: 'document.pdf',
133
+ fileType: 'application/pdf',
134
+ })).rejects.toThrow();
135
+ });
136
+
137
+ test('should fail for empty file name', async () => {
138
+ await expect(user1Caller.user.getUploadUrl({
139
+ fileName: '',
140
+ fileType: 'image/jpeg',
141
+ })).rejects.toThrow();
142
+ });
143
+
144
+ test('should fail without authentication', async () => {
145
+ const invalidCaller = await createTRPCContext({
146
+ req: { headers: {} } as any,
147
+ res: {} as any,
148
+ });
149
+ const router = appRouter.createCaller(invalidCaller);
150
+
151
+ await expect(router.user.getUploadUrl({
152
+ fileName: 'test.jpg',
153
+ fileType: 'image/jpeg',
154
+ })).rejects.toThrow();
155
+ });
156
+ });
157
+ });
158
+
package/vitest.config.ts CHANGED
@@ -1,11 +1,37 @@
1
1
  import { defineConfig } from 'vitest/config';
2
+ import { config } from 'dotenv';
3
+ import { resolve } from 'path';
4
+
5
+ // Load test environment variables before anything else
6
+ config({ path: resolve(process.cwd(), '.env.test'), override: true });
2
7
 
3
8
  export default defineConfig({
4
9
  test: {
5
10
  globals: true,
6
11
  environment: 'node',
7
12
  hookTimeout: 60000, // 60 seconds
13
+ testTimeout: 30000, // 30 seconds per test
8
14
  include: ['tests/**/*.test.ts'],
9
15
  setupFiles: ['./tests/setup.ts'], // run setup before tests
16
+ // Run tests sequentially to avoid database conflicts
17
+ // Can be changed to true once tests are fully isolated
18
+ sequence: {
19
+ concurrent: false,
20
+ },
21
+ coverage: {
22
+ exclude: [
23
+ "generated/**",
24
+ "prisma/**",
25
+ "scripts/**",
26
+ "src/lib/prisma.ts",
27
+ "src/seedDatabase.ts",
28
+ "src/socket/**",
29
+ "**/node_modules/**",
30
+ "**/dist/**",
31
+ ],
32
+ },
33
+ env: {
34
+ NODE_ENV: 'test',
35
+ },
10
36
  },
11
37
  });