1+ import { describe , it , expect , beforeEach , vi } from 'vitest' ;
2+ import { act } from '@testing-library/react' ;
3+ import { useProjectStore } from '../project' ;
4+ import { api } from '../../api' ;
5+
6+ // Mock the API client
7+ vi . mock ( '../../api' , ( ) => ( {
8+ api : {
9+ projects : {
10+ getProjects : vi . fn ( ) ,
11+ getProject : vi . fn ( ) ,
12+ createProject : vi . fn ( ) ,
13+ deleteProject : vi . fn ( ) ,
14+ getUploadUrl : vi . fn ( ) ,
15+ getProjectStatus : vi . fn ( ) ,
16+ } ,
17+ } ,
18+ } ) ) ;
19+
20+ // Mock fetch for file upload
21+ global . fetch = vi . fn ( ) ;
22+
23+ describe ( 'Project Store' , ( ) => {
24+ beforeEach ( ( ) => {
25+ // Reset store state
26+ useProjectStore . setState ( {
27+ projects : [ ] ,
28+ currentProject : null ,
29+ uploadStatus : null ,
30+ isLoading : false ,
31+ isCreating : false ,
32+ isDeleting : false ,
33+ isUploading : false ,
34+ error : null ,
35+ page : 1 ,
36+ limit : 10 ,
37+ total : 0 ,
38+ hasMore : false ,
39+ } ) ;
40+
41+ // Clear all mocks
42+ vi . clearAllMocks ( ) ;
43+ } ) ;
44+
45+ describe ( 'fetchProjects' , ( ) => {
46+ it ( 'should fetch projects successfully' , async ( ) => {
47+ const mockProjects = [
48+ { id : '1' , name : 'Project 1' , description : 'Test project 1' , status : 'ready' , user_id : 'user1' , created_at : new Date ( ) . toISOString ( ) , updated_at : new Date ( ) . toISOString ( ) } ,
49+ { id : '2' , name : 'Project 2' , description : 'Test project 2' , status : 'processing' , user_id : 'user1' , created_at : new Date ( ) . toISOString ( ) , updated_at : new Date ( ) . toISOString ( ) } ,
50+ ] ;
51+
52+ vi . mocked ( api . projects . getProjects ) . mockResolvedValue ( {
53+ success : true ,
54+ data : {
55+ items : mockProjects ,
56+ total : 2 ,
57+ page : 1 ,
58+ limit : 10 ,
59+ hasMore : false ,
60+ } ,
61+ } ) ;
62+
63+ const { fetchProjects } = useProjectStore . getState ( ) ;
64+
65+ await act ( async ( ) => {
66+ await fetchProjects ( ) ;
67+ } ) ;
68+
69+ const state = useProjectStore . getState ( ) ;
70+ expect ( state . projects ) . toEqual ( mockProjects ) ;
71+ expect ( state . total ) . toBe ( 2 ) ;
72+ expect ( state . hasMore ) . toBe ( false ) ;
73+ expect ( state . isLoading ) . toBe ( false ) ;
74+ expect ( state . error ) . toBeNull ( ) ;
75+ } ) ;
76+
77+ it ( 'should handle fetch error' , async ( ) => {
78+ vi . mocked ( api . projects . getProjects ) . mockResolvedValue ( {
79+ success : false ,
80+ error : 'Failed to fetch projects' ,
81+ } ) ;
82+
83+ const { fetchProjects } = useProjectStore . getState ( ) ;
84+
85+ await act ( async ( ) => {
86+ await fetchProjects ( ) ;
87+ } ) ;
88+
89+ const state = useProjectStore . getState ( ) ;
90+ expect ( state . projects ) . toEqual ( [ ] ) ;
91+ expect ( state . error ) . toBe ( 'Failed to fetch projects' ) ;
92+ expect ( state . isLoading ) . toBe ( false ) ;
93+ } ) ;
94+ } ) ;
95+
96+ describe ( 'createProject' , ( ) => {
97+ it ( 'should create project successfully' , async ( ) => {
98+ const newProject = {
99+ id : '3' ,
100+ name : 'New Project' ,
101+ description : 'A new test project' ,
102+ status : 'uploading' as const ,
103+ user_id : 'user1' ,
104+ created_at : new Date ( ) . toISOString ( ) ,
105+ updated_at : new Date ( ) . toISOString ( ) ,
106+ } ;
107+
108+ vi . mocked ( api . projects . createProject ) . mockResolvedValue ( {
109+ success : true ,
110+ data : {
111+ project : newProject ,
112+ upload_url : 'https://minio.example.com/upload' ,
113+ } ,
114+ } ) ;
115+
116+ const { createProject } = useProjectStore . getState ( ) ;
117+
118+ const result = await act ( async ( ) => {
119+ return await createProject ( { name : 'New Project' , description : 'A new test project' } ) ;
120+ } ) ;
121+
122+ expect ( result ) . toEqual ( newProject ) ;
123+
124+ const state = useProjectStore . getState ( ) ;
125+ expect ( state . projects ) . toContainEqual ( newProject ) ;
126+ expect ( state . currentProject ) . toEqual ( newProject ) ;
127+ expect ( state . isCreating ) . toBe ( false ) ;
128+ expect ( state . error ) . toBeNull ( ) ;
129+ } ) ;
130+
131+ it ( 'should handle create error' , async ( ) => {
132+ vi . mocked ( api . projects . createProject ) . mockResolvedValue ( {
133+ success : false ,
134+ error : 'Failed to create project' ,
135+ } ) ;
136+
137+ const { createProject } = useProjectStore . getState ( ) ;
138+
139+ const result = await act ( async ( ) => {
140+ return await createProject ( { name : 'New Project' , description : 'Test' } ) ;
141+ } ) ;
142+
143+ expect ( result ) . toBeNull ( ) ;
144+
145+ const state = useProjectStore . getState ( ) ;
146+ expect ( state . error ) . toBe ( 'Failed to create project' ) ;
147+ expect ( state . isCreating ) . toBe ( false ) ;
148+ } ) ;
149+ } ) ;
150+
151+ describe ( 'deleteProject' , ( ) => {
152+ it ( 'should delete project successfully' , async ( ) => {
153+ // Set initial projects
154+ useProjectStore . setState ( {
155+ projects : [
156+ { id : '1' , name : 'Project 1' , description : 'Test' , status : 'ready' , user_id : 'user1' , created_at : new Date ( ) . toISOString ( ) , updated_at : new Date ( ) . toISOString ( ) } ,
157+ { id : '2' , name : 'Project 2' , description : 'Test' , status : 'ready' , user_id : 'user1' , created_at : new Date ( ) . toISOString ( ) , updated_at : new Date ( ) . toISOString ( ) } ,
158+ ] ,
159+ } ) ;
160+
161+ vi . mocked ( api . projects . deleteProject ) . mockResolvedValue ( {
162+ success : true ,
163+ data : { message : 'Project deleted' } ,
164+ } ) ;
165+
166+ const { deleteProject } = useProjectStore . getState ( ) ;
167+
168+ const result = await act ( async ( ) => {
169+ return await deleteProject ( '1' ) ;
170+ } ) ;
171+
172+ expect ( result ) . toBe ( true ) ;
173+
174+ const state = useProjectStore . getState ( ) ;
175+ expect ( state . projects ) . toHaveLength ( 1 ) ;
176+ expect ( state . projects [ 0 ] . id ) . toBe ( '2' ) ;
177+ expect ( state . isDeleting ) . toBe ( false ) ;
178+ } ) ;
179+ } ) ;
180+
181+ describe ( 'uploadFile' , ( ) => {
182+ it ( 'should upload file successfully' , async ( ) => {
183+ const mockFile = new File ( [ 'test content' ] , 'test.csv' , { type : 'text/csv' } ) ;
184+
185+ vi . mocked ( api . projects . getUploadUrl ) . mockResolvedValue ( {
186+ success : true ,
187+ data : {
188+ upload_url : 'https://minio.example.com/upload' ,
189+ upload_fields : {
190+ key : 'test-key' ,
191+ policy : 'test-policy' ,
192+ } ,
193+ } ,
194+ } ) ;
195+
196+ vi . mocked ( api . projects . getProjectStatus ) . mockResolvedValue ( {
197+ success : true ,
198+ data : {
199+ status : 'processing' ,
200+ message : 'Processing file' ,
201+ progress : 50 ,
202+ } ,
203+ } ) ;
204+
205+ vi . mocked ( global . fetch ) . mockResolvedValue ( {
206+ ok : true ,
207+ } as Response ) ;
208+
209+ const { uploadFile } = useProjectStore . getState ( ) ;
210+
211+ const result = await act ( async ( ) => {
212+ return await uploadFile ( 'project1' , mockFile ) ;
213+ } ) ;
214+
215+ expect ( result ) . toBe ( true ) ;
216+ expect ( vi . mocked ( global . fetch ) ) . toHaveBeenCalledWith (
217+ 'https://minio.example.com/upload' ,
218+ expect . objectContaining ( {
219+ method : 'POST' ,
220+ body : expect . any ( FormData ) ,
221+ } )
222+ ) ;
223+
224+ const state = useProjectStore . getState ( ) ;
225+ expect ( state . isUploading ) . toBe ( false ) ;
226+ expect ( state . uploadStatus ) . toEqual ( {
227+ status : 'processing' ,
228+ message : 'Processing file' ,
229+ progress : 50 ,
230+ } ) ;
231+ } ) ;
232+
233+ it ( 'should handle upload error' , async ( ) => {
234+ const mockFile = new File ( [ 'test content' ] , 'test.csv' , { type : 'text/csv' } ) ;
235+
236+ vi . mocked ( api . projects . getUploadUrl ) . mockResolvedValue ( {
237+ success : false ,
238+ error : 'Failed to get upload URL' ,
239+ } ) ;
240+
241+ const { uploadFile } = useProjectStore . getState ( ) ;
242+
243+ const result = await act ( async ( ) => {
244+ return await uploadFile ( 'project1' , mockFile ) ;
245+ } ) ;
246+
247+ expect ( result ) . toBe ( false ) ;
248+
249+ const state = useProjectStore . getState ( ) ;
250+ expect ( state . error ) . toBe ( 'Failed to get upload URL' ) ;
251+ expect ( state . isUploading ) . toBe ( false ) ;
252+ } ) ;
253+ } ) ;
254+
255+ describe ( 'convenience hooks' , ( ) => {
256+ it ( 'useProjects should return projects data' , ( ) => {
257+ const mockProjects = [
258+ { id : '1' , name : 'Project 1' , description : 'Test' , status : 'ready' as const , user_id : 'user1' , created_at : new Date ( ) . toISOString ( ) , updated_at : new Date ( ) . toISOString ( ) } ,
259+ ] ;
260+
261+ useProjectStore . setState ( {
262+ projects : mockProjects ,
263+ isLoading : false ,
264+ error : null ,
265+ hasMore : false ,
266+ total : 1 ,
267+ } ) ;
268+
269+ const state = useProjectStore . getState ( ) ;
270+ expect ( state . projects ) . toEqual ( mockProjects ) ;
271+ expect ( state . isLoading ) . toBe ( false ) ;
272+ } ) ;
273+
274+ it ( 'setCurrentProject should update current project' , ( ) => {
275+ const project = {
276+ id : '1' ,
277+ name : 'Project 1' ,
278+ description : 'Test' ,
279+ status : 'ready' as const ,
280+ user_id : 'user1' ,
281+ created_at : new Date ( ) . toISOString ( ) ,
282+ updated_at : new Date ( ) . toISOString ( ) ,
283+ } ;
284+
285+ const { setCurrentProject } = useProjectStore . getState ( ) ;
286+
287+ act ( ( ) => {
288+ setCurrentProject ( project ) ;
289+ } ) ;
290+
291+ const state = useProjectStore . getState ( ) ;
292+ expect ( state . currentProject ) . toEqual ( project ) ;
293+ } ) ;
294+
295+ it ( 'reset should clear all state' , ( ) => {
296+ useProjectStore . setState ( {
297+ projects : [ { id : '1' , name : 'Test' , description : 'Test' , status : 'ready' , user_id : 'user1' , created_at : new Date ( ) . toISOString ( ) , updated_at : new Date ( ) . toISOString ( ) } ] ,
298+ currentProject : { id : '1' , name : 'Test' , description : 'Test' , status : 'ready' , user_id : 'user1' , created_at : new Date ( ) . toISOString ( ) , updated_at : new Date ( ) . toISOString ( ) } ,
299+ error : 'Some error' ,
300+ } ) ;
301+
302+ const { reset } = useProjectStore . getState ( ) ;
303+
304+ act ( ( ) => {
305+ reset ( ) ;
306+ } ) ;
307+
308+ const state = useProjectStore . getState ( ) ;
309+ expect ( state . projects ) . toEqual ( [ ] ) ;
310+ expect ( state . currentProject ) . toBeNull ( ) ;
311+ expect ( state . error ) . toBeNull ( ) ;
312+ } ) ;
313+ } ) ;
314+ } ) ;
0 commit comments