een-api-toolkit 0.0.18 → 0.1.4
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.
- package/CHANGELOG.md +8 -5
- package/README.md +29 -41
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +339 -821
- package/dist/index.js +295 -340
- package/dist/index.js.map +1 -1
- package/docs/AI-CONTEXT.md +151 -237
- package/examples/vue-bridges/.env.example +13 -0
- package/examples/vue-bridges/e2e/app.spec.ts +73 -0
- package/examples/vue-bridges/e2e/auth.spec.ts +206 -0
- package/examples/{vue-basic → vue-bridges}/index.html +1 -1
- package/examples/vue-bridges/package-lock.json +1583 -0
- package/examples/vue-bridges/package.json +28 -0
- package/examples/vue-bridges/playwright.config.ts +46 -0
- package/examples/vue-bridges/src/App.vue +108 -0
- package/examples/vue-bridges/src/router/index.ts +68 -0
- package/examples/vue-bridges/src/views/BridgeDetail.vue +279 -0
- package/examples/vue-bridges/src/views/Bridges.vue +297 -0
- package/examples/vue-bridges/src/views/Callback.vue +76 -0
- package/examples/vue-bridges/src/views/Home.vue +150 -0
- package/examples/vue-bridges/src/views/Login.vue +33 -0
- package/examples/vue-bridges/src/views/Logout.vue +66 -0
- package/examples/vue-bridges/src/vite-env.d.ts +12 -0
- package/examples/vue-cameras/e2e/app.spec.ts +2 -2
- package/examples/vue-cameras/e2e/auth.spec.ts +206 -0
- package/examples/vue-cameras/src/App.vue +4 -4
- package/examples/vue-cameras/src/views/CameraDetail.vue +57 -9
- package/examples/vue-cameras/src/views/Cameras.vue +69 -18
- package/examples/vue-cameras/src/views/Home.vue +36 -11
- package/examples/{vue-basic → vue-users}/README.md +4 -4
- package/examples/{vue-basic → vue-users}/e2e/app.spec.ts +3 -3
- package/examples/{vue-basic → vue-users}/e2e/auth.spec.ts +2 -2
- package/examples/vue-users/index.html +13 -0
- package/examples/{vue-basic → vue-users}/package-lock.json +3 -3
- package/examples/{vue-basic → vue-users}/package.json +1 -1
- package/examples/{vue-basic → vue-users}/src/App.vue +1 -1
- package/examples/vue-users/src/main.ts +23 -0
- package/examples/{vue-basic → vue-users}/src/views/Home.vue +27 -12
- package/examples/{vue-basic → vue-users}/src/views/Users.vue +51 -10
- package/examples/vue-users/tsconfig.json +21 -0
- package/examples/vue-users/tsconfig.node.json +10 -0
- package/examples/vue-users/vite.config.ts +12 -0
- package/package.json +1 -1
- /package/examples/{vue-basic → vue-bridges}/src/main.ts +0 -0
- /package/examples/{vue-basic → vue-bridges}/tsconfig.json +0 -0
- /package/examples/{vue-basic → vue-bridges}/tsconfig.node.json +0 -0
- /package/examples/{vue-basic → vue-bridges}/vite.config.ts +0 -0
- /package/examples/{vue-basic → vue-users}/.env.example +0 -0
- /package/examples/{vue-basic → vue-users}/playwright.config.ts +0 -0
- /package/examples/{vue-basic → vue-users}/src/router/index.ts +0 -0
- /package/examples/{vue-basic → vue-users}/src/views/Callback.vue +0 -0
- /package/examples/{vue-basic → vue-users}/src/views/Login.vue +0 -0
- /package/examples/{vue-basic → vue-users}/src/views/Logout.vue +0 -0
- /package/examples/{vue-basic → vue-users}/src/vite-env.d.ts +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { test, expect, Page } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* E2E tests for the Vue Bridges Example - OAuth Login Flow
|
|
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 and landing URL
|
|
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
|
+
const TIMEOUTS = {
|
|
20
|
+
OAUTH_REDIRECT: 30000,
|
|
21
|
+
ELEMENT_VISIBLE: 15000,
|
|
22
|
+
PASSWORD_VISIBLE: 10000,
|
|
23
|
+
AUTH_COMPLETE: 30000,
|
|
24
|
+
UI_UPDATE: 10000,
|
|
25
|
+
PROXY_CHECK: 5000
|
|
26
|
+
} as const
|
|
27
|
+
|
|
28
|
+
const TEST_USER = process.env.TEST_USER
|
|
29
|
+
const TEST_PASSWORD = process.env.TEST_PASSWORD
|
|
30
|
+
const PROXY_URL = process.env.VITE_PROXY_URL
|
|
31
|
+
|
|
32
|
+
async function isProxyAccessible(): Promise<boolean> {
|
|
33
|
+
if (!PROXY_URL) return false
|
|
34
|
+
const controller = new AbortController()
|
|
35
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUTS.PROXY_CHECK)
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch(PROXY_URL, {
|
|
39
|
+
method: 'HEAD',
|
|
40
|
+
signal: controller.signal
|
|
41
|
+
})
|
|
42
|
+
return response.ok || response.status === 404
|
|
43
|
+
} catch {
|
|
44
|
+
return false
|
|
45
|
+
} finally {
|
|
46
|
+
clearTimeout(timeoutId)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function performLogin(page: Page, username: string, password: string): Promise<void> {
|
|
51
|
+
await page.goto('/')
|
|
52
|
+
|
|
53
|
+
// Click login button on home page to go to login page
|
|
54
|
+
await page.click('[data-testid="login-button"]')
|
|
55
|
+
await page.waitForURL('/login')
|
|
56
|
+
|
|
57
|
+
// Click the OAuth login button on the login page
|
|
58
|
+
await Promise.all([
|
|
59
|
+
page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
|
|
60
|
+
page.click('button:has-text("Login with Eagle Eye Networks")')
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
const emailInput = page.locator('#authentication--input__email')
|
|
64
|
+
await emailInput.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
65
|
+
await emailInput.fill(username)
|
|
66
|
+
|
|
67
|
+
await page.getByRole('button', { name: 'Next' }).click()
|
|
68
|
+
|
|
69
|
+
const passwordInput = page.locator('#authentication--input__password')
|
|
70
|
+
await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUTS.PASSWORD_VISIBLE })
|
|
71
|
+
await passwordInput.fill(password)
|
|
72
|
+
|
|
73
|
+
await page.locator('#next, button:has-text("Sign in")').first().click()
|
|
74
|
+
|
|
75
|
+
// After login, the bridges app redirects to /bridges
|
|
76
|
+
await page.waitForURL('**/bridges', { timeout: TIMEOUTS.AUTH_COMPLETE })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function clearAuthState(page: Page): Promise<void> {
|
|
80
|
+
try {
|
|
81
|
+
const url = page.url()
|
|
82
|
+
if (url && url.startsWith('http')) {
|
|
83
|
+
await page.evaluate(() => {
|
|
84
|
+
try {
|
|
85
|
+
localStorage.clear()
|
|
86
|
+
sessionStorage.clear()
|
|
87
|
+
} catch {
|
|
88
|
+
// Ignore
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Ignore
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
test.describe('Vue Bridges Example - Auth', () => {
|
|
98
|
+
let proxyAccessible = false
|
|
99
|
+
|
|
100
|
+
function skipIfNoProxy() {
|
|
101
|
+
test.skip(!proxyAccessible, 'OAuth proxy not accessible')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function skipIfNoCredentials() {
|
|
105
|
+
test.skip(!TEST_USER || !TEST_PASSWORD, 'Test credentials not available')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
test.beforeAll(async () => {
|
|
109
|
+
proxyAccessible = await isProxyAccessible()
|
|
110
|
+
if (!proxyAccessible) {
|
|
111
|
+
console.log('OAuth proxy not accessible - OAuth tests will be skipped')
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test.afterEach(async ({ page }) => {
|
|
116
|
+
await clearAuthState(page)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('shows login button when not authenticated', async ({ page }) => {
|
|
120
|
+
await page.goto('/')
|
|
121
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
|
|
122
|
+
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
123
|
+
await expect(page.locator('[data-testid="nav-logout"]')).not.toBeVisible()
|
|
124
|
+
await expect(page.locator('[data-testid="nav-bridges"]')).not.toBeVisible()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('bridges page redirects to login when not authenticated', async ({ page }) => {
|
|
128
|
+
await page.goto('/bridges')
|
|
129
|
+
// Should redirect to login page
|
|
130
|
+
await expect(page.locator('h2')).toContainText('Login')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('login button redirects to OAuth page', async ({ page }) => {
|
|
134
|
+
skipIfNoProxy()
|
|
135
|
+
skipIfNoCredentials()
|
|
136
|
+
|
|
137
|
+
await page.goto('/')
|
|
138
|
+
await expect(page.locator('[data-testid="login-button"]')).toBeVisible()
|
|
139
|
+
|
|
140
|
+
// Click login button to go to login page
|
|
141
|
+
await page.click('[data-testid="login-button"]')
|
|
142
|
+
await page.waitForURL('/login')
|
|
143
|
+
|
|
144
|
+
// Click the OAuth login button
|
|
145
|
+
await Promise.all([
|
|
146
|
+
page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
|
|
147
|
+
page.click('button:has-text("Login with Eagle Eye Networks")')
|
|
148
|
+
])
|
|
149
|
+
|
|
150
|
+
const emailInput = page.locator('#authentication--input__email')
|
|
151
|
+
await expect(emailInput).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('complete OAuth login flow and verify landing URL', async ({ page }) => {
|
|
155
|
+
skipIfNoProxy()
|
|
156
|
+
skipIfNoCredentials()
|
|
157
|
+
|
|
158
|
+
// Verify initially not authenticated
|
|
159
|
+
await page.goto('/')
|
|
160
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
|
|
161
|
+
|
|
162
|
+
// Perform login
|
|
163
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
164
|
+
|
|
165
|
+
// Verify landing URL is the bridges page (vue-bridges app redirects to /bridges after login)
|
|
166
|
+
await expect(page).toHaveURL('http://127.0.0.1:3333/bridges')
|
|
167
|
+
|
|
168
|
+
// Verify authenticated state - we're on bridges page so check nav elements
|
|
169
|
+
await expect(page.locator('[data-testid="nav-bridges"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
170
|
+
await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible()
|
|
171
|
+
await expect(page.locator('[data-testid="nav-login"]')).not.toBeVisible()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('can view bridges list after login', async ({ page }) => {
|
|
175
|
+
skipIfNoProxy()
|
|
176
|
+
skipIfNoCredentials()
|
|
177
|
+
|
|
178
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
179
|
+
await expect(page.locator('[data-testid="nav-bridges"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
180
|
+
|
|
181
|
+
// Navigate to bridges page
|
|
182
|
+
await page.click('[data-testid="nav-bridges"]')
|
|
183
|
+
await page.waitForURL('/bridges')
|
|
184
|
+
|
|
185
|
+
// Verify landing URL is bridges page
|
|
186
|
+
await expect(page).toHaveURL('http://127.0.0.1:3333/bridges')
|
|
187
|
+
|
|
188
|
+
// Should see bridges grid (not error state)
|
|
189
|
+
await expect(page.locator('.bridge-grid, .no-bridges')).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
190
|
+
await expect(page.locator('.error')).not.toBeVisible()
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('can logout after login', async ({ page }) => {
|
|
194
|
+
skipIfNoProxy()
|
|
195
|
+
skipIfNoCredentials()
|
|
196
|
+
|
|
197
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
198
|
+
await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
199
|
+
|
|
200
|
+
await page.click('[data-testid="nav-logout"]')
|
|
201
|
+
|
|
202
|
+
await page.waitForURL('**/')
|
|
203
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
204
|
+
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
205
|
+
})
|
|
206
|
+
})
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>EEN
|
|
7
|
+
<title>EEN Bridges Example</title>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="app"></div>
|