een-api-toolkit 0.0.8 → 0.0.11

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.
@@ -0,0 +1,260 @@
1
+ import { test, expect, Page } from '@playwright/test'
2
+
3
+ /**
4
+ * E2E tests for the Vue Basic Example
5
+ *
6
+ * Tests the OAuth login flow through the UI:
7
+ * 1. Click login button in the example app
8
+ * 2. Enter credentials on EEN OAuth page
9
+ * 3. Complete the OAuth callback
10
+ * 4. Verify authenticated state
11
+ *
12
+ * Required environment variables:
13
+ * - VITE_PROXY_URL: OAuth proxy URL (e.g., http://127.0.0.1:8787)
14
+ * - VITE_EEN_CLIENT_ID: EEN OAuth client ID
15
+ * - TEST_USER: Test user email
16
+ * - TEST_PASSWORD: Test user password
17
+ */
18
+
19
+ // Timeout constants for consistent behavior
20
+ // Values chosen based on OAuth flow timing requirements
21
+ const TIMEOUTS = {
22
+ OAUTH_REDIRECT: 30000, // OAuth redirects can be slow on first load
23
+ ELEMENT_VISIBLE: 15000, // Wait for OAuth page elements to render
24
+ PASSWORD_VISIBLE: 10000, // Password field appears after email validation
25
+ AUTH_COMPLETE: 30000, // Full OAuth flow completion
26
+ UI_UPDATE: 10000, // UI state updates after auth changes
27
+ PROXY_CHECK: 5000 // Quick check if proxy is running
28
+ } as const
29
+
30
+ const TEST_USER = process.env.TEST_USER
31
+ const TEST_PASSWORD = process.env.TEST_PASSWORD
32
+ const PROXY_URL = process.env.VITE_PROXY_URL
33
+
34
+ /**
35
+ * Checks if the OAuth proxy is accessible.
36
+ * Returns true if proxy responds (even with 404), false if unreachable.
37
+ */
38
+ async function isProxyAccessible(): Promise<boolean> {
39
+ if (!PROXY_URL) return false
40
+ const controller = new AbortController()
41
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUTS.PROXY_CHECK)
42
+
43
+ try {
44
+ const response = await fetch(PROXY_URL, {
45
+ method: 'HEAD',
46
+ signal: controller.signal
47
+ })
48
+ // 404 is ok - means proxy is running but endpoint doesn't exist
49
+ return response.ok || response.status === 404
50
+ } catch {
51
+ return false
52
+ } finally {
53
+ clearTimeout(timeoutId)
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Performs OAuth login flow through the UI.
59
+ * Starts from home page and completes full OAuth authentication.
60
+ */
61
+ async function performLogin(page: Page, username: string, password: string): Promise<void> {
62
+ // Start at home page
63
+ await page.goto('/')
64
+
65
+ // Click login button and wait for OAuth redirect
66
+ await Promise.all([
67
+ page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
68
+ page.click('[data-testid="login-button"]')
69
+ ])
70
+
71
+ // Fill email
72
+ const emailInput = page.locator('#authentication--input__email')
73
+ await emailInput.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE })
74
+ await emailInput.fill(username)
75
+
76
+ // Click next
77
+ await page.getByRole('button', { name: 'Next' }).click()
78
+
79
+ // Fill password
80
+ const passwordInput = page.locator('#authentication--input__password')
81
+ await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUTS.PASSWORD_VISIBLE })
82
+ await passwordInput.fill(password)
83
+
84
+ // Click sign in - use OR selector for robustness
85
+ await page.locator('#next, button:has-text("Sign in")').first().click()
86
+
87
+ // Wait for auth to complete and redirect to app
88
+ await page.waitForURL('**/', { timeout: TIMEOUTS.AUTH_COMPLETE })
89
+ }
90
+
91
+ /**
92
+ * Clears browser storage to reset auth state.
93
+ * Handles cases where localStorage isn't accessible (e.g., about:blank, cross-origin).
94
+ */
95
+ async function clearAuthState(page: Page): Promise<void> {
96
+ try {
97
+ // Only try to clear storage if we're on a page that allows it
98
+ const url = page.url()
99
+ if (url && url.startsWith('http')) {
100
+ await page.evaluate(() => {
101
+ try {
102
+ localStorage.clear()
103
+ sessionStorage.clear()
104
+ } catch {
105
+ // Ignore errors - storage may not be accessible
106
+ }
107
+ })
108
+ }
109
+ } catch {
110
+ // Ignore errors - page may be closed or in an inaccessible state
111
+ }
112
+ }
113
+
114
+ test.describe('Vue Basic Example', () => {
115
+ // Check proxy accessibility once before all tests
116
+ let proxyAccessible = false
117
+
118
+ // Helper functions to skip tests when prerequisites aren't met
119
+ function skipIfNoProxy() {
120
+ test.skip(!proxyAccessible, 'OAuth proxy not accessible')
121
+ }
122
+
123
+ function skipIfNoCredentials() {
124
+ test.skip(!TEST_USER || !TEST_PASSWORD, 'Test credentials not available')
125
+ }
126
+
127
+ function skipIfNoUser() {
128
+ test.skip(!TEST_USER, 'Test user not available')
129
+ }
130
+
131
+ test.beforeAll(async () => {
132
+ proxyAccessible = await isProxyAccessible()
133
+ if (!proxyAccessible) {
134
+ console.log('OAuth proxy not accessible - OAuth tests will be skipped')
135
+ }
136
+ })
137
+
138
+ test.afterEach(async ({ page }) => {
139
+ // Clear auth state after each test to prevent state pollution
140
+ await clearAuthState(page)
141
+ })
142
+
143
+ test('shows login button when not authenticated', async ({ page }) => {
144
+ await page.goto('/')
145
+ await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
146
+ await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
147
+ await expect(page.locator('[data-testid="nav-logout"]')).not.toBeVisible()
148
+ })
149
+
150
+ test('users page shows not-authenticated state without login', async ({ page }) => {
151
+ await page.goto('/users')
152
+ await expect(
153
+ page.locator('[data-testid="not-authenticated"], [data-testid="nav-login"], .error, .auth-required').first()
154
+ ).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
155
+ })
156
+
157
+ test('login button redirects to OAuth page', async ({ page }) => {
158
+ skipIfNoProxy()
159
+ skipIfNoCredentials()
160
+
161
+ await page.goto('/')
162
+ await expect(page.locator('[data-testid="login-button"]')).toBeVisible()
163
+ await expect(page.locator('[data-testid="login-button"]')).toBeEnabled()
164
+
165
+ // Click login and verify redirect to OAuth page
166
+ await Promise.all([
167
+ page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
168
+ page.click('[data-testid="login-button"]')
169
+ ])
170
+
171
+ // Verify we're on the OAuth page
172
+ const emailInput = page.locator('#authentication--input__email')
173
+ await expect(emailInput).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
174
+ })
175
+
176
+ test('complete OAuth login flow', async ({ page }) => {
177
+ skipIfNoProxy()
178
+ skipIfNoCredentials()
179
+
180
+ // Verify initially not authenticated
181
+ await page.goto('/')
182
+ await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
183
+
184
+ // Perform login
185
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
186
+
187
+ // Verify authenticated state
188
+ await expect(page.locator('[data-testid="not-authenticated"]')).not.toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
189
+ await expect(page.locator('[data-testid="nav-users"]')).toBeVisible()
190
+ await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible()
191
+ await expect(page.locator('[data-testid="nav-login"]')).not.toBeVisible()
192
+ })
193
+
194
+ test('can view users list after login', async ({ page }) => {
195
+ skipIfNoProxy()
196
+ skipIfNoCredentials()
197
+
198
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
199
+ await expect(page.locator('[data-testid="nav-users"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
200
+
201
+ // Navigate to users page
202
+ await page.click('[data-testid="nav-users"]')
203
+ await page.waitForURL('/users')
204
+
205
+ // Should see users table (not error state)
206
+ await expect(page.locator('.users table')).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
207
+ await expect(page.locator('.error')).not.toBeVisible()
208
+ })
209
+
210
+ test('can logout after login', async ({ page }) => {
211
+ skipIfNoProxy()
212
+ skipIfNoCredentials()
213
+
214
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
215
+ await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
216
+
217
+ // Click logout
218
+ await page.click('[data-testid="nav-logout"]')
219
+
220
+ // Should show not authenticated
221
+ await page.waitForURL('**/')
222
+ await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
223
+ await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
224
+ })
225
+
226
+ test('invalid password shows error on OAuth page', async ({ page }) => {
227
+ skipIfNoProxy()
228
+ skipIfNoUser()
229
+
230
+ await page.goto('/')
231
+
232
+ // Click login and wait for OAuth redirect
233
+ await Promise.all([
234
+ page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
235
+ page.click('[data-testid="login-button"]')
236
+ ])
237
+
238
+ // Fill valid email
239
+ const emailInput = page.locator('#authentication--input__email')
240
+ await emailInput.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE })
241
+ await emailInput.fill(TEST_USER!)
242
+ await page.getByRole('button', { name: 'Next' }).click()
243
+
244
+ // Fill invalid password
245
+ const passwordInput = page.locator('#authentication--input__password')
246
+ await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUTS.PASSWORD_VISIBLE })
247
+ await passwordInput.fill('invalid-password-12345!')
248
+
249
+ // Click sign in
250
+ await page.locator('#next, button:has-text("Sign in")').first().click()
251
+
252
+ // Should show error message on OAuth page
253
+ await expect(
254
+ page.locator('.error, [class*="error"], [data-testid*="error"], #error, .alert-danger').first()
255
+ ).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
256
+
257
+ // Should still be on OAuth page
258
+ await expect(page).toHaveURL(/eagleeyenetworks\.com/)
259
+ })
260
+ })
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>EEN API Toolkit Example</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>