1+ /// <reference types="cypress" />
2+
3+ describe ( 'Authentication Flow' , ( ) => {
4+ beforeEach ( ( ) => {
5+ // Clear any existing authentication state
6+ cy . clearLocalStorage ( ) ;
7+ cy . clearCookies ( ) ;
8+ } ) ;
9+
10+ describe ( 'Login Page' , ( ) => {
11+ beforeEach ( ( ) => {
12+ cy . visit ( '/auth/login' ) ;
13+ } ) ;
14+
15+ it ( 'should display login form' , ( ) => {
16+ cy . get ( '[data-cy=login-form]' ) . should ( 'be.visible' ) ;
17+ cy . get ( '[data-cy=email-input]' ) . should ( 'be.visible' ) ;
18+ cy . get ( '[data-cy=password-input]' ) . should ( 'be.visible' ) ;
19+ cy . get ( '[data-cy=login-button]' ) . should ( 'be.visible' ) ;
20+ } ) ;
21+
22+ it ( 'should show validation errors for empty form submission' , ( ) => {
23+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
24+
25+ cy . get ( '[data-cy=email-error]' ) . should ( 'contain' , 'Email is required' ) ;
26+ cy . get ( '[data-cy=password-error]' ) . should ( 'contain' , 'Password is required' ) ;
27+ } ) ;
28+
29+ it ( 'should show validation error for invalid email format' , ( ) => {
30+ cy . get ( '[data-cy=email-input]' ) . type ( 'invalid-email' ) ;
31+ cy . get ( '[data-cy=email-input]' ) . blur ( ) ;
32+
33+ cy . get ( '[data-cy=email-error]' ) . should ( 'contain' , 'Please enter a valid email' ) ;
34+ } ) ;
35+
36+ it ( 'should show validation error for short password' , ( ) => {
37+ cy . get ( '[data-cy=password-input]' ) . type ( '123' ) ;
38+ cy . get ( '[data-cy=password-input]' ) . blur ( ) ;
39+
40+ cy . get ( '[data-cy=password-error]' ) . should ( 'contain' , 'Password must be at least 6 characters' ) ;
41+ } ) ;
42+
43+ it ( 'should toggle password visibility' , ( ) => {
44+ cy . get ( '[data-cy=password-input]' ) . should ( 'have.attr' , 'type' , 'password' ) ;
45+
46+ cy . get ( '[data-cy=password-toggle]' ) . click ( ) ;
47+ cy . get ( '[data-cy=password-input]' ) . should ( 'have.attr' , 'type' , 'text' ) ;
48+
49+ cy . get ( '[data-cy=password-toggle]' ) . click ( ) ;
50+ cy . get ( '[data-cy=password-input]' ) . should ( 'have.attr' , 'type' , 'password' ) ;
51+ } ) ;
52+
53+ it ( 'should login successfully with valid credentials' , ( ) => {
54+ cy . get ( '[data-cy=email-input]' ) . type ( 'demo@concepter.com' ) ;
55+ cy . get ( '[data-cy=password-input]' ) . type ( 'demo123' ) ;
56+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
57+
58+ // Should redirect to dashboard
59+ cy . url ( ) . should ( 'include' , '/dashboard' ) ;
60+
61+ // Should show user info in header
62+ cy . get ( '[data-cy=user-menu]' ) . should ( 'be.visible' ) ;
63+ } ) ;
64+
65+ it ( 'should show error message for invalid credentials' , ( ) => {
66+ cy . get ( '[data-cy=email-input]' ) . type ( 'wrong@example.com' ) ;
67+ cy . get ( '[data-cy=password-input]' ) . type ( 'wrongpassword' ) ;
68+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
69+
70+ cy . get ( '[data-cy=error-message]' ) . should ( 'be.visible' ) ;
71+ cy . get ( '[data-cy=error-message]' ) . should ( 'contain' , 'Invalid credentials' ) ;
72+ } ) ;
73+
74+ it ( 'should remember user preference' , ( ) => {
75+ cy . get ( '[data-cy=email-input]' ) . type ( 'demo@concepter.com' ) ;
76+ cy . get ( '[data-cy=password-input]' ) . type ( 'demo123' ) ;
77+ cy . get ( '[data-cy=remember-me]' ) . check ( ) ;
78+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
79+
80+ // Verify remember me is checked
81+ cy . get ( '[data-cy=remember-me]' ) . should ( 'be.checked' ) ;
82+ } ) ;
83+
84+ it ( 'should navigate to register page' , ( ) => {
85+ cy . get ( '[data-cy=register-link]' ) . click ( ) ;
86+ cy . url ( ) . should ( 'include' , '/auth/register' ) ;
87+ } ) ;
88+ } ) ;
89+
90+ describe ( 'Registration Page' , ( ) => {
91+ beforeEach ( ( ) => {
92+ cy . visit ( '/auth/register' ) ;
93+ } ) ;
94+
95+ it ( 'should display registration form' , ( ) => {
96+ cy . get ( '[data-cy=register-form]' ) . should ( 'be.visible' ) ;
97+ cy . get ( '[data-cy=first-name-input]' ) . should ( 'be.visible' ) ;
98+ cy . get ( '[data-cy=last-name-input]' ) . should ( 'be.visible' ) ;
99+ cy . get ( '[data-cy=email-input]' ) . should ( 'be.visible' ) ;
100+ cy . get ( '[data-cy=password-input]' ) . should ( 'be.visible' ) ;
101+ cy . get ( '[data-cy=register-button]' ) . should ( 'be.visible' ) ;
102+ } ) ;
103+
104+ it ( 'should complete registration flow' , ( ) => {
105+ // Fill basic information
106+ cy . get ( '[data-cy=first-name-input]' ) . type ( 'John' ) ;
107+ cy . get ( '[data-cy=last-name-input]' ) . type ( 'Doe' ) ;
108+ cy . get ( '[data-cy=email-input]' ) . type ( 'john.doe@example.com' ) ;
109+ cy . get ( '[data-cy=password-input]' ) . type ( 'password123' ) ;
110+
111+ // Fill preferences if using stepper
112+ cy . get ( '[data-cy=next-button]' ) . click ( ) ;
113+ cy . get ( '[data-cy=event-format-select]' ) . click ( ) ;
114+ cy . get ( '[data-cy=hybrid-option]' ) . click ( ) ;
115+ cy . get ( '[data-cy=industry-select]' ) . click ( ) ;
116+ cy . get ( '[data-cy=technology-option]' ) . click ( ) ;
117+
118+ cy . get ( '[data-cy=register-button]' ) . click ( ) ;
119+
120+ // Should redirect to dashboard after successful registration
121+ cy . url ( ) . should ( 'include' , '/dashboard' ) ;
122+ cy . get ( '[data-cy=user-menu]' ) . should ( 'contain' , 'John Doe' ) ;
123+ } ) ;
124+
125+ it ( 'should navigate back to login page' , ( ) => {
126+ cy . get ( '[data-cy=login-link]' ) . click ( ) ;
127+ cy . url ( ) . should ( 'include' , '/auth/login' ) ;
128+ } ) ;
129+ } ) ;
130+
131+ describe ( 'Authentication State' , ( ) => {
132+ it ( 'should redirect unauthenticated users to login' , ( ) => {
133+ cy . visit ( '/dashboard' ) ;
134+ cy . url ( ) . should ( 'include' , '/auth/login' ) ;
135+
136+ cy . visit ( '/concepts' ) ;
137+ cy . url ( ) . should ( 'include' , '/auth/login' ) ;
138+
139+ cy . visit ( '/profile' ) ;
140+ cy . url ( ) . should ( 'include' , '/auth/login' ) ;
141+ } ) ;
142+
143+ it ( 'should allow access to protected routes after login' , ( ) => {
144+ // Login first
145+ cy . visit ( '/auth/login' ) ;
146+ cy . get ( '[data-cy=email-input]' ) . type ( 'demo@concepter.com' ) ;
147+ cy . get ( '[data-cy=password-input]' ) . type ( 'demo123' ) ;
148+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
149+
150+ // Should be able to access protected routes
151+ cy . visit ( '/dashboard' ) ;
152+ cy . url ( ) . should ( 'include' , '/dashboard' ) ;
153+
154+ cy . visit ( '/concepts' ) ;
155+ cy . url ( ) . should ( 'include' , '/concepts' ) ;
156+
157+ cy . visit ( '/profile' ) ;
158+ cy . url ( ) . should ( 'include' , '/profile' ) ;
159+ } ) ;
160+
161+ it ( 'should persist authentication across browser refresh' , ( ) => {
162+ // Login
163+ cy . visit ( '/auth/login' ) ;
164+ cy . get ( '[data-cy=email-input]' ) . type ( 'demo@concepter.com' ) ;
165+ cy . get ( '[data-cy=password-input]' ) . type ( 'demo123' ) ;
166+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
167+
168+ // Refresh the page
169+ cy . reload ( ) ;
170+
171+ // Should still be authenticated
172+ cy . url ( ) . should ( 'include' , '/dashboard' ) ;
173+ cy . get ( '[data-cy=user-menu]' ) . should ( 'be.visible' ) ;
174+ } ) ;
175+ } ) ;
176+
177+ describe ( 'Logout' , ( ) => {
178+ beforeEach ( ( ) => {
179+ // Login before each logout test
180+ cy . visit ( '/auth/login' ) ;
181+ cy . get ( '[data-cy=email-input]' ) . type ( 'demo@concepter.com' ) ;
182+ cy . get ( '[data-cy=password-input]' ) . type ( 'demo123' ) ;
183+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
184+ cy . url ( ) . should ( 'include' , '/dashboard' ) ;
185+ } ) ;
186+
187+ it ( 'should logout successfully' , ( ) => {
188+ cy . get ( '[data-cy=user-menu]' ) . click ( ) ;
189+ cy . get ( '[data-cy=logout-button]' ) . click ( ) ;
190+
191+ // Should redirect to login page
192+ cy . url ( ) . should ( 'include' , '/auth/login' ) ;
193+
194+ // Should not be able to access protected routes
195+ cy . visit ( '/dashboard' ) ;
196+ cy . url ( ) . should ( 'include' , '/auth/login' ) ;
197+ } ) ;
198+
199+ it ( 'should clear authentication state after logout' , ( ) => {
200+ cy . get ( '[data-cy=user-menu]' ) . click ( ) ;
201+ cy . get ( '[data-cy=logout-button]' ) . click ( ) ;
202+
203+ // Check that localStorage is cleared
204+ cy . window ( ) . then ( ( window ) => {
205+ expect ( window . localStorage . getItem ( 'access_token' ) ) . to . be . null ;
206+ expect ( window . localStorage . getItem ( 'current_user' ) ) . to . be . null ;
207+ } ) ;
208+ } ) ;
209+ } ) ;
210+
211+ describe ( 'Loading States' , ( ) => {
212+ it ( 'should show loading spinner during login' , ( ) => {
213+ cy . visit ( '/auth/login' ) ;
214+
215+ // Intercept the login API call to add delay
216+ cy . intercept ( 'POST' , '/api/auth/login' , { delay : 1000 } ) . as ( 'loginRequest' ) ;
217+
218+ cy . get ( '[data-cy=email-input]' ) . type ( 'demo@concepter.com' ) ;
219+ cy . get ( '[data-cy=password-input]' ) . type ( 'demo123' ) ;
220+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
221+
222+ // Should show loading spinner
223+ cy . get ( '[data-cy=loading-spinner]' ) . should ( 'be.visible' ) ;
224+
225+ cy . wait ( '@loginRequest' ) ;
226+
227+ // Loading spinner should disappear
228+ cy . get ( '[data-cy=loading-spinner]' ) . should ( 'not.exist' ) ;
229+ } ) ;
230+ } ) ;
231+
232+ describe ( 'Error Handling' , ( ) => {
233+ it ( 'should handle network errors gracefully' , ( ) => {
234+ cy . visit ( '/auth/login' ) ;
235+
236+ // Simulate network error
237+ cy . intercept ( 'POST' , '/api/auth/login' , { forceNetworkError : true } ) . as ( 'loginError' ) ;
238+
239+ cy . get ( '[data-cy=email-input]' ) . type ( 'demo@concepter.com' ) ;
240+ cy . get ( '[data-cy=password-input]' ) . type ( 'demo123' ) ;
241+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
242+
243+ // Should show error message
244+ cy . get ( '[data-cy=error-message]' ) . should ( 'be.visible' ) ;
245+ cy . get ( '[data-cy=error-message]' ) . should ( 'contain' , 'Network error' ) ;
246+ } ) ;
247+
248+ it ( 'should handle server errors gracefully' , ( ) => {
249+ cy . visit ( '/auth/login' ) ;
250+
251+ // Simulate server error
252+ cy . intercept ( 'POST' , '/api/auth/login' , {
253+ statusCode : 500 ,
254+ body : { message : 'Internal server error' }
255+ } ) . as ( 'serverError' ) ;
256+
257+ cy . get ( '[data-cy=email-input]' ) . type ( 'demo@concepter.com' ) ;
258+ cy . get ( '[data-cy=password-input]' ) . type ( 'demo123' ) ;
259+ cy . get ( '[data-cy=login-button]' ) . click ( ) ;
260+
261+ // Should show error message
262+ cy . get ( '[data-cy=error-message]' ) . should ( 'be.visible' ) ;
263+ cy . get ( '[data-cy=error-message]' ) . should ( 'contain' , 'server error' ) ;
264+ } ) ;
265+ } ) ;
266+
267+ describe ( 'Accessibility' , ( ) => {
268+ it ( 'should be keyboard navigable' , ( ) => {
269+ cy . visit ( '/auth/login' ) ;
270+
271+ // Tab through form elements
272+ cy . get ( 'body' ) . type ( '{tab}' ) ;
273+ cy . focused ( ) . should ( 'have.attr' , 'formControlName' , 'email' ) ;
274+
275+ cy . focused ( ) . type ( '{tab}' ) ;
276+ cy . focused ( ) . should ( 'have.attr' , 'formControlName' , 'password' ) ;
277+
278+ cy . focused ( ) . type ( '{tab}' ) ;
279+ cy . focused ( ) . should ( 'have.attr' , 'type' , 'submit' ) ;
280+ } ) ;
281+
282+ it ( 'should have proper ARIA labels' , ( ) => {
283+ cy . visit ( '/auth/login' ) ;
284+
285+ cy . get ( '[data-cy=email-input]' ) . should ( 'have.attr' , 'aria-label' ) ;
286+ cy . get ( '[data-cy=password-input]' ) . should ( 'have.attr' , 'aria-label' ) ;
287+ cy . get ( '[data-cy=login-button]' ) . should ( 'have.attr' , 'aria-label' ) ;
288+ } ) ;
289+ } ) ;
290+ } ) ;
0 commit comments