Skip to content

Commit 47e09d2

Browse files
authored
Support database container3 (#202)
* chore: support database container * chore: fix chat * chore: fix database tab views * fix: database test * fix: page action * fix: test * chore: rename * fix: navigate
1 parent 1042e51 commit 47e09d2

File tree

62 files changed

+5866
-1319
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+5866
-1319
lines changed

cypress/e2e/auth/oauth-login.cy.ts

Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { v4 as uuidv4 } from 'uuid';
22
import { TestConfig, generateRandomEmail } from '../../support/test-config';
3+
import { APIResponseCode } from '../../support/api-mocks';
34

45
/**
56
* OAuth Login Flow Tests
@@ -22,14 +23,77 @@ import { TestConfig, generateRandomEmail } from '../../support/test-config';
2223
describe('OAuth Login Flow', () => {
2324
const { baseUrl, gotrueUrl, apiUrl } = TestConfig;
2425

26+
/**
27+
* Sets up mocks for app initialization endpoints.
28+
* These prevent 401/404 errors from unmocked endpoints triggering session invalidation.
29+
*/
30+
const setupAppInitMocks = (mockWorkspaceId: string) => {
31+
// Mock favorites endpoint
32+
cy.intercept('GET', `${apiUrl}/api/workspace/*/favorite`, {
33+
statusCode: 200,
34+
body: { code: APIResponseCode.Success, data: { views: [] }, message: 'Success' },
35+
}).as('getFavorites');
36+
37+
// Mock folder/outline endpoint
38+
cy.intercept('GET', `${apiUrl}/api/workspace/*/folder*`, {
39+
statusCode: 200,
40+
body: {
41+
code: APIResponseCode.Success,
42+
data: {
43+
view_id: mockWorkspaceId,
44+
name: 'My Workspace',
45+
children: [],
46+
layout: 0,
47+
icon: null,
48+
extra: null,
49+
is_private: false,
50+
is_published: false,
51+
},
52+
message: 'Success',
53+
},
54+
}).as('getFolder');
55+
56+
// Mock trash endpoint
57+
cy.intercept('GET', `${apiUrl}/api/workspace/*/trash`, {
58+
statusCode: 200,
59+
body: { code: APIResponseCode.Success, data: { views: [] }, message: 'Success' },
60+
}).as('getTrash');
61+
62+
// Mock recent endpoint
63+
cy.intercept('GET', `${apiUrl}/api/workspace/*/recent`, {
64+
statusCode: 200,
65+
body: { code: APIResponseCode.Success, data: { views: [] }, message: 'Success' },
66+
}).as('getRecent');
67+
68+
// Mock user update endpoint (for timezone)
69+
cy.intercept('POST', `${apiUrl}/api/user/update`, {
70+
statusCode: 200,
71+
body: { code: APIResponseCode.Success, message: 'Success' },
72+
}).as('updateUserProfile');
73+
74+
// Mock workspace open endpoint
75+
cy.intercept('PUT', `${apiUrl}/api/workspace/*/open`, {
76+
statusCode: 200,
77+
body: { code: APIResponseCode.Success, message: 'Success' },
78+
}).as('openWorkspace');
79+
80+
// Mock shareWithMe endpoint
81+
cy.intercept('GET', `${apiUrl}/api/workspace?*`, {
82+
statusCode: 200,
83+
body: { code: APIResponseCode.Success, data: [], message: 'Success' },
84+
}).as('shareWithMe');
85+
};
86+
2587
beforeEach(() => {
2688
// Handle uncaught exceptions
2789
cy.on('uncaught:exception', (err) => {
2890
if (
2991
err.message.includes('Minified React error') ||
3092
err.message.includes('View not found') ||
3193
err.message.includes('No workspace or service found') ||
32-
err.message.includes('Cannot read properties of undefined')
94+
err.message.includes('Cannot read properties of undefined') ||
95+
err.message.includes('WebSocket') ||
96+
err.message.includes('ResizeObserver loop')
3397
) {
3498
return false;
3599
}
@@ -53,11 +117,14 @@ describe('OAuth Login Flow', () => {
53117

54118
cy.log(`[TEST START] Testing OAuth login for new user: ${testEmail}`);
55119

120+
// Setup mocks for app initialization endpoints
121+
setupAppInitMocks(mockWorkspaceId);
122+
56123
// Mock the verifyToken endpoint - new user
57124
cy.intercept('GET', `${apiUrl}/api/user/verify/${mockAccessToken}`, {
58125
statusCode: 200,
59126
body: {
60-
code: 0,
127+
code: APIResponseCode.Success,
61128
data: {
62129
is_new: true,
63130
},
@@ -86,7 +153,7 @@ describe('OAuth Login Flow', () => {
86153
cy.intercept('GET', `${apiUrl}/api/user/workspace`, {
87154
statusCode: 200,
88155
body: {
89-
code: 0,
156+
code: APIResponseCode.Success,
90157
data: {
91158
user_profile: { uuid: mockUserId },
92159
visiting_workspace: {
@@ -116,17 +183,19 @@ describe('OAuth Login Flow', () => {
116183
},
117184
}).as('getUserWorkspaceInfo');
118185

119-
// Mock getCurrentUser endpoint
186+
// Mock getCurrentUser endpoint (include timezone metadata to avoid update call)
120187
cy.intercept('GET', `${apiUrl}/api/user/profile*`, {
121188
statusCode: 200,
122189
body: {
123-
code: 0,
190+
code: APIResponseCode.Success,
124191
data: {
125192
uid: 1,
126193
uuid: mockUserId,
127194
email: testEmail,
128195
name: 'Test User',
129-
metadata: {},
196+
metadata: {
197+
'0': { default_timezone: 'UTC', timezone: 'UTC' },
198+
},
130199
encryption_sign: null,
131200
latest_workspace_id: mockWorkspaceId,
132201
updated_at: Date.now(),
@@ -211,11 +280,14 @@ describe('OAuth Login Flow', () => {
211280

212281
cy.log(`[TEST START] Testing OAuth login for existing user: ${testEmail}`);
213282

283+
// Setup mocks for app initialization endpoints
284+
setupAppInitMocks(mockWorkspaceId);
285+
214286
// Mock the verifyToken endpoint - existing user
215287
cy.intercept('GET', `${apiUrl}/api/user/verify/${mockAccessToken}`, {
216288
statusCode: 200,
217289
body: {
218-
code: 0,
290+
code: APIResponseCode.Success,
219291
data: {
220292
is_new: false,
221293
},
@@ -244,7 +316,7 @@ describe('OAuth Login Flow', () => {
244316
cy.intercept('GET', `${apiUrl}/api/user/workspace`, {
245317
statusCode: 200,
246318
body: {
247-
code: 0,
319+
code: APIResponseCode.Success,
248320
data: {
249321
user_profile: { uuid: mockUserId },
250322
visiting_workspace: {
@@ -274,17 +346,19 @@ describe('OAuth Login Flow', () => {
274346
},
275347
}).as('getUserWorkspaceInfo');
276348

277-
// Mock getCurrentUser endpoint
349+
// Mock getCurrentUser endpoint (include timezone metadata to avoid update call)
278350
cy.intercept('GET', `${apiUrl}/api/user/profile*`, {
279351
statusCode: 200,
280352
body: {
281-
code: 0,
353+
code: APIResponseCode.Success,
282354
data: {
283355
uid: 1,
284356
uuid: mockUserId,
285357
email: testEmail,
286358
name: 'Test User',
287-
metadata: {},
359+
metadata: {
360+
'0': { default_timezone: 'UTC', timezone: 'UTC' },
361+
},
288362
encryption_sign: null,
289363
latest_workspace_id: mockWorkspaceId,
290364
updated_at: Date.now(),
@@ -361,11 +435,14 @@ describe('OAuth Login Flow', () => {
361435

362436
cy.log('[TEST START] Testing redirect loop prevention');
363437

438+
// Setup mocks for app initialization endpoints
439+
setupAppInitMocks(mockWorkspaceId);
440+
364441
// Mock all required endpoints
365442
cy.intercept('GET', `${apiUrl}/api/user/verify/${mockAccessToken}`, {
366443
statusCode: 200,
367444
body: {
368-
code: 0,
445+
code: APIResponseCode.Success,
369446
data: { is_new: false },
370447
message: 'Success',
371448
},
@@ -390,7 +467,7 @@ describe('OAuth Login Flow', () => {
390467
cy.intercept('GET', `${apiUrl}/api/user/workspace`, {
391468
statusCode: 200,
392469
body: {
393-
code: 0,
470+
code: APIResponseCode.Success,
394471
data: {
395472
user_profile: { uuid: mockUserId },
396473
visiting_workspace: {
@@ -423,13 +500,15 @@ describe('OAuth Login Flow', () => {
423500
cy.intercept('GET', `${apiUrl}/api/user/profile*`, {
424501
statusCode: 200,
425502
body: {
426-
code: 0,
503+
code: APIResponseCode.Success,
427504
data: {
428505
uid: 1,
429506
uuid: mockUserId,
430507
431508
name: 'Test User',
432-
metadata: {},
509+
metadata: {
510+
'0': { default_timezone: 'UTC', timezone: 'UTC' },
511+
},
433512
encryption_sign: null,
434513
latest_workspace_id: mockWorkspaceId,
435514
updated_at: Date.now(),
@@ -496,6 +575,9 @@ describe('OAuth Login Flow', () => {
496575

497576
cy.log('[TEST START] Testing old expired token race condition');
498577

578+
// Setup mocks for app initialization endpoints
579+
setupAppInitMocks(mockWorkspaceId);
580+
499581
cy.log('[SETUP] Pre-populate localStorage with expired token');
500582
cy.window().then((win) => {
501583
// Set old expired token (expired 1 hour ago)
@@ -550,7 +632,7 @@ describe('OAuth Login Flow', () => {
550632
cy.intercept('GET', `${apiUrl}/api/user/verify/${newAccessToken}`, {
551633
statusCode: 200,
552634
body: {
553-
code: 0,
635+
code: APIResponseCode.Success,
554636
data: { is_new: false },
555637
message: 'Success',
556638
},
@@ -569,7 +651,7 @@ describe('OAuth Login Flow', () => {
569651
cy.intercept('GET', `${apiUrl}/api/user/workspace`, {
570652
statusCode: 200,
571653
body: {
572-
code: 0,
654+
code: APIResponseCode.Success,
573655
data: {
574656
user_profile: { uuid: mockUserId },
575657
visiting_workspace: {
@@ -602,13 +684,15 @@ describe('OAuth Login Flow', () => {
602684
cy.intercept('GET', `${apiUrl}/api/user/profile*`, {
603685
statusCode: 200,
604686
body: {
605-
code: 0,
687+
code: APIResponseCode.Success,
606688
data: {
607689
uid: 1,
608690
uuid: mockUserId,
609691
610692
name: 'Test User',
611-
metadata: {},
693+
metadata: {
694+
'0': { default_timezone: 'UTC', timezone: 'UTC' },
695+
},
612696
encryption_sign: null,
613697
latest_workspace_id: mockWorkspaceId,
614698
updated_at: Date.now(),

cypress/e2e/chat/chat-input.cy.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AuthTestUtils } from '../../support/auth-utils';
22
import { TestTool } from '../../support/page-utils';
3-
import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors } from '../../support/selectors';
3+
import { AddPageSelectors, ModelSelectorSelectors, PageSelectors, SidebarSelectors, ChatSelectors, byTestId } from '../../support/selectors';
44
import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config';
55

66
describe('Chat Input Tests', () => {
@@ -34,8 +34,8 @@ describe('Chat Input Tests', () => {
3434
authUtils.signInWithTestUrl(testEmail).then(() => {
3535
cy.url().should('include', '/app');
3636

37-
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
38-
PageSelectors.items().should('exist', { timeout: 30000 });
37+
SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible');
38+
PageSelectors.items({ timeout: 30000 }).should('exist');
3939
cy.wait(2000);
4040

4141
TestTool.expandSpace();
@@ -52,17 +52,19 @@ describe('Chat Input Tests', () => {
5252
AddPageSelectors.addAIChatButton().should('be.visible').click();
5353

5454
cy.wait(2000);
55+
ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible');
5556

5657
// Test 1: Format toggle
5758
cy.log('Testing format toggle');
58-
ChatSelectors.formatGroup().then($group => {
59-
if ($group.length > 0) {
59+
cy.get('body').then(($body) => {
60+
if ($body.find(byTestId('chat-format-group')).length > 0) {
6061
ChatSelectors.formatToggle().click();
6162
ChatSelectors.formatGroup().should('not.exist');
6263
}
6364
});
6465

65-
ChatSelectors.formatToggle().should('be.visible').click();
66+
ChatSelectors.formatToggle({ timeout: 30000 }).should('be.visible');
67+
ChatSelectors.formatToggle().click();
6668
ChatSelectors.formatGroup().should('exist');
6769
ChatSelectors.formatGroup().find('button').should('have.length.at.least', 4);
6870
ChatSelectors.formatToggle().click();
@@ -111,8 +113,8 @@ describe('Chat Input Tests', () => {
111113
authUtils.signInWithTestUrl(testEmail).then(() => {
112114
cy.url().should('include', '/app');
113115

114-
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
115-
PageSelectors.items().should('exist', { timeout: 30000 });
116+
SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible');
117+
PageSelectors.items({ timeout: 30000 }).should('exist');
116118
cy.wait(2000);
117119

118120
TestTool.expandSpace();
@@ -129,6 +131,7 @@ describe('Chat Input Tests', () => {
129131
AddPageSelectors.addAIChatButton().should('be.visible').click();
130132

131133
cy.wait(3000); // Wait for chat to fully load
134+
ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible');
132135

133136
// Mock API endpoints with more realistic responses
134137
cy.intercept('POST', '**/api/chat/**/message/question', (req) => {

cypress/e2e/chat/create-ai-chat.cy.ts

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,18 @@ describe('AI Chat Creation and Navigation Tests', () => {
5353
cy.get('body', { timeout: 30000 }).should('not.contain', 'Welcome!');
5454

5555
// Wait for the sidebar to be visible (indicates app is loaded)
56-
SidebarSelectors.pageHeader().should('be.visible', { timeout: 30000 });
56+
SidebarSelectors.pageHeader({ timeout: 30000 }).should('be.visible');
5757

5858
// Wait for at least one page to exist in the sidebar
59-
PageSelectors.names().should('exist', { timeout: 30000 });
59+
PageSelectors.names({ timeout: 30000 }).should('exist');
6060

6161
// Additional wait for stability
6262
cy.wait(2000);
6363

6464
// Now wait for the new page button to be available
6565
testLog.info( 'Looking for new page button...');
66-
PageSelectors.newPageButton()
67-
.should('exist', { timeout: 20000 })
66+
PageSelectors.newPageButton({ timeout: 20000 })
67+
.should('exist')
6868
.then(() => {
6969
testLog.info( 'New page button found!');
7070
});
@@ -121,33 +121,12 @@ describe('AI Chat Creation and Navigation Tests', () => {
121121
testLog.info( '=== Step 4: Verifying AI Chat page loaded ===');
122122

123123
// Check that the URL contains a view ID (indicating navigation to chat)
124-
cy.url().should('match', /\/app\/[a-f0-9-]+\/[a-f0-9-]+/, { timeout: 10000 });
124+
cy.url({ timeout: 20000 }).should('match', /\/app\/[^/]+\/[^/?#]+/);
125125
testLog.info( '✓ Navigated to AI Chat page');
126126

127-
// Check if the AI Chat container exists (but don't fail if it doesn't load immediately)
128-
ChatSelectors.aiChatContainer().then($container => {
129-
if ($container.length > 0) {
130-
testLog.info( '✓ AI Chat container exists');
131-
} else {
132-
testLog.info( 'AI Chat container not immediately visible, checking for navigation success...');
133-
}
134-
});
135-
136-
// Wait a bit for the chat to fully load
137-
cy.wait(2000);
138-
139-
// Check for AI Chat specific elements (the chat interface)
140-
// The AI chat library loads its own components
141-
cy.get('body').then($body => {
142-
ChatSelectors.aiChatContainer().then($container => {
143-
const hasChatElements = $body.find('.ai-chat').length > 0 || $container.length > 0;
144-
if (hasChatElements) {
145-
testLog.info( '✓ AI Chat interface loaded');
146-
} else {
147-
testLog.info( 'Warning: AI Chat elements not immediately visible, but container exists');
148-
}
149-
});
150-
});
127+
// Verify AI Chat container renders (chat UI may load async after container is mounted)
128+
ChatSelectors.aiChatContainer({ timeout: 30000 }).should('be.visible');
129+
testLog.info( '✓ AI Chat container exists');
151130

152131
// Verify no error messages are displayed
153132
cy.get('body').then($body => {

0 commit comments

Comments
 (0)