refs-integration.test.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { createServer, type Server } from 'node:http';
  2. import { renderHook, waitFor } from '@testing-library/react';
  3. import axios from 'axios';
  4. import express from 'express';
  5. import {
  6. afterAll,
  7. beforeAll,
  8. beforeEach,
  9. describe,
  10. expect,
  11. it,
  12. vi,
  13. } from 'vitest';
  14. import { useSWRxRef, useSWRxRefs } from '../src/client/stores/refs';
  15. import { routesFactory } from '../src/server/routes/refs';
  16. // Test server setup
  17. let testServer: Server;
  18. let baseURL: string;
  19. // Mock data for testing
  20. const mockUser = {
  21. _id: 'user123',
  22. username: 'testuser',
  23. email: 'test@example.com',
  24. };
  25. const mockPage = {
  26. _id: 'page123',
  27. path: '/test/page',
  28. };
  29. const mockAttachment = {
  30. _id: 'attachment123',
  31. originalName: 'test-file.pdf',
  32. fileName: 'test-file.pdf',
  33. fileFormat: 'pdf',
  34. filePath: '/uploads/test-file.pdf',
  35. filePathProxied: '/api/attachments/attachment123',
  36. downloadPathProxied: '/api/attachments/attachment123/download',
  37. fileSize: 1024,
  38. attachmentType: 'file',
  39. page: 'page123',
  40. creator: 'user123',
  41. createdAt: '2023-01-01T00:00:00.000Z', // Use string format to match JSON serialization
  42. };
  43. const mockAttachments = [
  44. {
  45. _id: 'attachment1',
  46. originalName: 'file1.pdf',
  47. fileName: 'file1.pdf',
  48. filePath: '/uploads/file1.pdf',
  49. creator: mockUser._id,
  50. page: mockPage._id, // This should match the page ID returned by the page query
  51. createdAt: '2023-01-01T00:00:00.000Z', // JSON serialized format
  52. fileFormat: 'pdf',
  53. fileSize: 1024,
  54. attachmentType: 'file',
  55. filePathProxied: '/api/attachments/attachment1',
  56. downloadPathProxied: '/api/attachments/attachment1/download',
  57. },
  58. {
  59. _id: 'attachment2',
  60. originalName: 'file2.jpg',
  61. fileName: 'file2.jpg',
  62. filePath: '/uploads/file2.jpg',
  63. creator: mockUser._id,
  64. page: mockPage._id, // This should match the page ID returned by the page query
  65. createdAt: '2023-01-01T00:00:00.000Z', // JSON serialized format
  66. fileFormat: 'image',
  67. fileSize: 2048,
  68. attachmentType: 'image',
  69. filePathProxied: '/api/attachments/attachment2',
  70. downloadPathProxied: '/api/attachments/attachment2/download',
  71. },
  72. ];
  73. // Mock the Growi dependencies that the routes need
  74. const mockCrowi = {
  75. require: vi.fn((path) => {
  76. if (path === '../middlewares/login-required') {
  77. return () => (_req, _res, next) => {
  78. // Mock authentication - add user to request
  79. _req.user = mockUser;
  80. next();
  81. };
  82. }
  83. return vi.fn();
  84. }),
  85. accessTokenParser: (_req, _res, next) => next(),
  86. };
  87. // Mock mongoose models
  88. const mockPageModel = {
  89. findByPathAndViewer: vi.fn(),
  90. isAccessiblePageByViewer: vi.fn().mockResolvedValue(true),
  91. find: vi.fn().mockImplementation(() => {
  92. // Return a mock query that has chaining methods
  93. const mockQuery = {
  94. select: vi.fn().mockReturnThis(),
  95. exec: vi.fn().mockResolvedValue([
  96. { id: mockPage._id }, // Return our mock page ID
  97. ]),
  98. };
  99. return mockQuery;
  100. }),
  101. addConditionToFilteringByViewerForList: vi.fn(),
  102. PageQueryBuilder: vi.fn().mockImplementation((query) => ({
  103. query: query || mockPageModel.find(), // Use the actual query or fall back to find()
  104. addConditionToListWithDescendants: vi.fn().mockReturnThis(),
  105. addConditionToExcludeTrashed: vi.fn().mockReturnThis(),
  106. })),
  107. };
  108. const mockAttachmentModel = {
  109. findOne: vi.fn(),
  110. find: vi.fn().mockImplementation(() => {
  111. // Return a mock query that supports chaining
  112. const mockQuery = {
  113. and: vi.fn().mockReturnThis(),
  114. populate: vi.fn().mockReturnThis(),
  115. exec: vi.fn().mockResolvedValue(mockAttachments), // Return our mock attachments
  116. };
  117. return mockQuery;
  118. }),
  119. and: vi.fn().mockReturnThis(),
  120. populate: vi.fn().mockReturnThis(),
  121. exec: vi.fn().mockResolvedValue(mockAttachments),
  122. };
  123. // Mock mongoose
  124. vi.mock('mongoose', () => ({
  125. default: {
  126. model: vi.fn((modelName) => {
  127. if (modelName === 'Page') return mockPageModel;
  128. if (modelName === 'Attachment') return mockAttachmentModel;
  129. return {};
  130. }),
  131. },
  132. model: vi.fn((modelName) => {
  133. if (modelName === 'Attachment') return mockAttachmentModel;
  134. return {};
  135. }),
  136. Types: {
  137. ObjectId: class MockObjectId {
  138. private id: string;
  139. constructor(id: string) {
  140. this.id = id;
  141. }
  142. static isValid(_id: unknown): boolean {
  143. return true; // Accept any ID as valid for testing
  144. }
  145. toString(): string {
  146. return this.id;
  147. }
  148. },
  149. },
  150. }));
  151. // Mock the serializer
  152. vi.mock('@growi/core/dist/models/serializers', () => ({
  153. serializeAttachmentSecurely: vi.fn((attachment) => attachment),
  154. }));
  155. describe('refs hooks - Integration Tests with Actual Routes', () => {
  156. beforeAll(async () => {
  157. // Create a real Express app with the actual routes
  158. const app = express();
  159. app.use(express.json());
  160. app.use(express.urlencoded({ extended: true }));
  161. // Create the routes using the real routesFactory with our mocks
  162. const router = routesFactory(mockCrowi);
  163. app.use('/_api/attachment-refs', router);
  164. // Start the test server on a dynamic port
  165. testServer = createServer(app);
  166. await new Promise<void>((resolve) => {
  167. testServer.listen(0, () => {
  168. const address = testServer.address();
  169. if (address && typeof address === 'object') {
  170. const port = address.port;
  171. baseURL = `http://localhost:${port}`;
  172. console.log(`Test server started on ${baseURL}`);
  173. }
  174. resolve();
  175. });
  176. });
  177. });
  178. afterAll(async () => {
  179. // Clean up the test server
  180. if (testServer) {
  181. await new Promise<void>((resolve) => {
  182. testServer.close(() => resolve());
  183. });
  184. }
  185. });
  186. beforeEach(() => {
  187. // Reset all mocks before each test
  188. vi.clearAllMocks();
  189. // Setup default mock behaviors
  190. mockPageModel.findByPathAndViewer.mockResolvedValue(mockPage);
  191. mockPageModel.isAccessiblePageByViewer.mockResolvedValue(true);
  192. mockAttachmentModel.findOne.mockImplementation(() => ({
  193. populate: vi.fn().mockResolvedValue(mockAttachment),
  194. }));
  195. mockAttachmentModel.find.mockReturnValue({
  196. and: vi.fn().mockReturnThis(),
  197. populate: vi.fn().mockReturnThis(),
  198. exec: vi.fn().mockResolvedValue(mockAttachments),
  199. });
  200. });
  201. describe('SWR Hook Integration Tests', () => {
  202. it('should test useSWRxRef hook with real server requests', async () => {
  203. // Arrange
  204. const pagePath = '/test/page';
  205. const fileNameOrId = 'test-file.pdf';
  206. // Configure axios to use our test server
  207. const originalDefaults = axios.defaults.baseURL;
  208. axios.defaults.baseURL = baseURL;
  209. try {
  210. console.log(`Testing with baseURL: ${baseURL}`);
  211. // Act - Test the hook with real server
  212. const { result } = renderHook(() =>
  213. useSWRxRef(pagePath, fileNameOrId, false),
  214. );
  215. // Wait for the hook to complete the request
  216. await waitFor(
  217. () => {
  218. console.log('Hook result:', result.current);
  219. expect(result.current.data).toBeDefined();
  220. },
  221. { timeout: 5000 },
  222. );
  223. // Assert - Hook should return attachment data from real server
  224. expect(result.current.data).toEqual(mockAttachment);
  225. expect(result.current.error).toBeUndefined();
  226. } finally {
  227. // Restore original axios defaults
  228. axios.defaults.baseURL = originalDefaults;
  229. }
  230. });
  231. it('should test useSWRxRefs hook with real server requests', async () => {
  232. // Arrange
  233. const pagePath = '/test/page';
  234. const prefix = '/test';
  235. const options = { depth: '1', regexp: '.*\\.pdf$' };
  236. // Configure axios to use our test server
  237. const originalDefaults = axios.defaults.baseURL;
  238. axios.defaults.baseURL = baseURL;
  239. try {
  240. console.log(`Testing with baseURL: ${baseURL}`);
  241. // Act - Test the hook with real server
  242. const { result } = renderHook(() =>
  243. useSWRxRefs(pagePath, prefix, options, false),
  244. );
  245. // Wait for the hook to complete the request
  246. await waitFor(
  247. () => {
  248. console.log('Hook result:', result.current);
  249. expect(result.current.data).toBeDefined();
  250. },
  251. { timeout: 5000 },
  252. );
  253. // Assert - Hook should return attachments data from real server
  254. expect(result.current.data).toEqual(mockAttachments);
  255. expect(result.current.error).toBeUndefined();
  256. } finally {
  257. // Restore original axios defaults
  258. axios.defaults.baseURL = originalDefaults;
  259. }
  260. });
  261. });
  262. });