Skip to content

Commit 0662f0e

Browse files
authored
feat(auth)!: rework storage state and authentication system (#40)
BREAKING CHANGE: The authentication system now uses cookie instead of local storage - Replace file-based storage state (keep as deprecated) with API-based authentication - Remove deprecated StorageState class and related utilities - Consolidate storage state operations into modular services - Update test fixtures to use new authentication approach - Improve clipboard handling in UI components - Add new test user for authentication testing - Fix toolbar title selector in operation page
1 parent 00f90ca commit 0662f0e

File tree

36 files changed

+410
-407
lines changed

36 files changed

+410
-407
lines changed

src/fixtures/fixtures.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
/* eslint-disable no-empty-pattern */
22
import { type Page, test as base } from '@playwright/test'
3-
import { getAuthDataFromStorageStateFile } from '@services/auth'
4-
import { SS_SYSADMIN_PATH, SS_USER1_PATH } from '@services/storage-state'
5-
import { createRest, type Rest } from '@services/rest'
6-
import {
7-
type AgentTestDataManager,
8-
type ApihubTestDataManager,
9-
createAgentTDM,
10-
createApihubTDM,
11-
createUsersTDM,
12-
type UsersTestDataManager,
13-
} from '@services/test-data-manager'
14-
import { BASE_ORIGIN } from '@test-setup'
3+
import { createRestWithCredentials, type Rest } from '@services/rest'
4+
import { type AgentTestDataManager, type ApihubTestDataManager, createAgentTDM, createApihubTDM, createUsersTDM, type UsersTestDataManager } from '@services/test-data-manager'
5+
import { BASE_URL } from '@test-setup'
6+
import { SYSADMIN, TEST_USER_1 } from '@test-data'
7+
import { createUserStorageStateWithAuthCookieFromApi } from '@services/storage-state/save'
158

169
export type Fixtures = {
1710

@@ -29,23 +22,22 @@ export type Fixtures = {
2922
export const test = base.extend<Fixtures>({
3023

3124
restApihub: async ({}, use) => {
32-
const authData = await getAuthDataFromStorageStateFile(SS_SYSADMIN_PATH)
33-
const rest = await createRest(BASE_ORIGIN, authData.token)
25+
const rest = await createRestWithCredentials(BASE_URL, SYSADMIN)
3426
await use(rest)
3527
},
3628

3729
usersTDM: async ({}, use) => {
38-
const creator = await createUsersTDM(SS_SYSADMIN_PATH)
30+
const creator = await createUsersTDM(SYSADMIN)
3931
await use(creator)
4032
},
4133

4234
apihubTDM: async ({}, use) => {
43-
const creator = await createApihubTDM(SS_SYSADMIN_PATH)
35+
const creator = await createApihubTDM(SYSADMIN)
4436
await use(creator)
4537
},
4638

4739
apihubTdmLongTimeout: async ({}, use) => {
48-
const creator = await createApihubTDM(SS_SYSADMIN_PATH, 600000)
40+
const creator = await createApihubTDM(SYSADMIN, 600000)
4941
await use(creator)
5042
},
5143

@@ -55,14 +47,14 @@ export const test = base.extend<Fixtures>({
5547
},
5648

5749
sysadminPage: async ({ browser }, use) => {
58-
const context = await browser.newContext({ storageState: SS_SYSADMIN_PATH })
50+
const context = await browser.newContext({ storageState: await createUserStorageStateWithAuthCookieFromApi(SYSADMIN) })
5951
const page = await context.newPage()
6052
await use(page)
6153
await context.close()
6254
},
6355

6456
user1Page: async ({ browser }, use) => {
65-
const context = await browser.newContext({ storageState: SS_USER1_PATH })
57+
const context = await browser.newContext({ storageState: await createUserStorageStateWithAuthCookieFromApi(TEST_USER_1) })
6658
const page = await context.newPage()
6759
await use(page)
6860
await context.close()

src/packages/portal/pages/PortalPage/BaseVersionPage/ConfigureVersionTab/ConfigurePackagesSubTab/AddPackageDialog.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ export class AddPackageDialog extends BaseAddDialog {
1919
}): Promise<void> {
2020
if (params.workspaceName) {
2121
await this.workspaceAc.click()
22+
await this.workspaceAc.fill(params.workspaceName)
2223
await this.workspaceAc.getListItem(params.workspaceName).click()
2324
}
2425
await this.packageAc.click()
26+
await this.packageAc.fill(params.packageId)
2527
await this.packageAc.getListItem(params.packageId, { exact: false }).click()
2628
await this.versionAc.click()
29+
await this.versionAc.fill(params.version)
2730
await this.versionAc.getListItem(params.version, { exact: false }).click()
2831
}
2932
}

src/packages/portal/pages/PortalPage/BaseVersionPage/PackageSettingsPage/AccessTokensTab.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ export class AccessTokensTab extends BaseSettingsTab {
5050
await this.tokenValueTxtFld.copyBtn.click()
5151
await report.step('Get token from the clipboard', async () => {
5252
await this.page.waitForTimeout(2000)
53-
str = await this.mainLocator.evaluate('navigator.clipboard.readText()')
53+
str = await this.page.evaluate(async () => {
54+
return await navigator.clipboard.readText()
55+
})
5456
})
5557
return str
5658
}

src/packages/portal/pages/PortalPage/OperationPage/OperationPageToolbar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class OperationPageToolbar {
99
.or(this.page.locator('.MuiCardHeader-root')) // TODO Add 'MainPageCardHeader' in APIHUBUI
1010
readonly breadcrumbs = new Breadcrumbs(this.locator.getByTestId('PackageBreadcrumbs'), 'Operation')
1111
readonly backBtn = new Button(this.locator.getByTestId('BackButton'), 'Back')
12-
readonly title = new Title(this.locator.getByTestId('ToolbarTitleTypography'), 'Package')
12+
readonly title = new Title(this.locator.getByTestId('ToolbarTitle'), 'Operation')
1313
readonly operationSlt = new OperationSelect(this.locator)
1414
readonly docBtn = new Button(this.locator.getByTestId('ModeButton-doc'), 'Doc')
1515
readonly simpleBtn = new Button(this.locator.getByTestId('ModeButton-simple'), 'Simple')

src/packages/portal/pages/PortalPage/VersionPage/VersionPackagePage/DocumentsPackageTab/DocumentsActionMenu.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ export class DocumentsActionMenu extends DropdownMenu {
3535
await report.step('Copy public link to source', async () => {
3636
await this.copyPublicLinkItm.click()
3737
await this.page.waitForTimeout(2000)
38-
str = await this.mainLocator.evaluate('navigator.clipboard.readText()')
38+
str = await this.page.evaluate(async () => {
39+
return await navigator.clipboard.readText()
40+
})
3941
})
4042
return str
4143
}
@@ -45,7 +47,9 @@ export class DocumentsActionMenu extends DropdownMenu {
4547
await report.step('Copy page template', async () => {
4648
await this.copyPageTemplateItm.click()
4749
await this.page.waitForTimeout(2000)
48-
str = await this.mainLocator.evaluate('navigator.clipboard.readText()')
50+
str = await this.page.evaluate(async () => {
51+
return await navigator.clipboard.readText()
52+
})
4953
})
5054
return str
5155
}

src/packages/shared/pages/BasePage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type Page, test as report } from '@playwright/test'
22
import { BaseComponent, Link } from '@shared/components/base'
33
import type { CloseOptions, DownloadedTestFile, GotoOptions, ReloadOptions } from '@shared/entities'
4-
import { BASE_ORIGIN } from '@test-setup'
4+
import { BASE_URL } from '@test-setup'
55
import { getDownloadedFile } from '@services/utils'
66

77
export class BasePage {
@@ -11,7 +11,7 @@ export class BasePage {
1111
constructor(protected readonly page: Page) { }
1212

1313
async goto(url: string, options?: GotoOptions): Promise<void> {
14-
const _url = new URL(url, BASE_ORIGIN).toString()
14+
const _url = new URL(url, BASE_URL.origin).toString()
1515
await report.step(`Go to "${url}"`, async () => {
1616
await this.page.goto(_url, options)
1717
})

src/packages/shared/pages/LoginPage.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class LoginPage extends BasePage {
2121
if (url) {
2222
await super.goto(url, options)
2323
} else {
24-
await this.navigationStep('Go to the "Login" page', '/login')
24+
await this.navigationStep('Go to the "Login" page', '/login?noAutoLogin=true')
2525
}
2626
}
2727

@@ -31,4 +31,11 @@ export class LoginPage extends BasePage {
3131
await this.signInBtn.click()
3232
await this.page.waitForLoadState()
3333
}
34+
35+
async signInNotInTest(credentials: Credentials): Promise<void> {
36+
await this.loginTxtFld.mainLocator.fill(credentials.email)
37+
await this.passwordTxtFld.mainLocator.fill(credentials.password)
38+
await this.signInBtn.mainLocator.click()
39+
await this.page.waitForLoadState()
40+
}
3441
}

src/services/auth/auth.ts

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import type { APIResponse, Page } from '@playwright/test'
1+
import type { APIResponse } from '@playwright/test'
22
import { request } from '@playwright/test'
33
import type { Credentials } from '@shared/entities'
4-
import type { StorageStateDto } from '@services/storage-state'
5-
import { StorageState } from '@services/storage-state'
64
import { DEFAULT_REQUEST_TIMEOUT, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_TIMEOUT } from '@services/rest'
7-
import { readFile } from 'fs/promises'
8-
import { asyncTimeout } from '@services/utils'
5+
import { asyncTimeout, getRestFailMsg } from '@services/utils'
96

10-
export type AuthData = {
7+
export interface AuthData {
118
token: string
129
renewToken: string
1310
user: {
@@ -18,20 +15,7 @@ export type AuthData = {
1815
}
1916
}
2017

21-
async function localAuth(url: string, credentials: Credentials): Promise<APIResponse> {
22-
const auth = Buffer.from(`${credentials.email}:${credentials.password}`).toString('base64')
23-
const requestContext = await request.newContext({
24-
baseURL: url,
25-
ignoreHTTPSErrors: true,
26-
extraHTTPHeaders: {
27-
'Authorization': `Basic ${auth}`,
28-
},
29-
timeout: DEFAULT_REQUEST_TIMEOUT,
30-
})
31-
return await requestContext.post('/api/v2/auth/local')
32-
}
33-
34-
export async function getAuthDataFromAPI(url: string, credentials: Credentials): Promise<AuthData> {
18+
export const getAuthDataFromApi = async (url: URL, credentials: Credentials): Promise<AuthData> => {
3519
let response!: APIResponse
3620
for (let i = 0; i < DEFAULT_RETRY_COUNT; i++) {
3721
response = await localAuth(url, credentials)
@@ -40,25 +24,22 @@ export async function getAuthDataFromAPI(url: string, credentials: Credentials):
4024
if (!jsonData.token) {
4125
continue
4226
}
43-
return await response.json()
27+
return jsonData as AuthData
4428
}
4529
await asyncTimeout(DEFAULT_RETRY_TIMEOUT)
4630
}
47-
throw new Error(`Getting Auth Data via API for "${credentials.email}" has been failed:\n ${await response.text()}`)
48-
}
49-
50-
export async function getAuthDataFromStorageState(storageStateDto: StorageStateDto): Promise<AuthData> {
51-
const ss = new StorageState(storageStateDto)
52-
return ss.getAuthData()
53-
}
54-
55-
export async function getAuthDataFromStorageStateFile(path: string): Promise<AuthData> {
56-
const contentString = (await readFile(path, 'utf8')).toString()
57-
const storageStateDto = JSON.parse(contentString)
58-
return await getAuthDataFromStorageState(storageStateDto)
31+
throw new Error(await getRestFailMsg(`Getting Auth Data from API for "${credentials.email}"`, response))
5932
}
6033

61-
export async function getAuthDataFromPage(page: Page): Promise<AuthData> {
62-
const storageStateDto = await page.context().storageState()
63-
return await getAuthDataFromStorageState(storageStateDto)
34+
async function localAuth(url: URL, credentials: Credentials): Promise<APIResponse> {
35+
const auth = Buffer.from(`${credentials.email}:${credentials.password}`).toString('base64')
36+
const requestContext = await request.newContext({
37+
baseURL: url.origin,
38+
ignoreHTTPSErrors: true,
39+
extraHTTPHeaders: {
40+
'Authorization': `Basic ${auth}`,
41+
},
42+
timeout: DEFAULT_REQUEST_TIMEOUT,
43+
})
44+
return await requestContext.post('/api/v2/auth/local')
6445
}

src/services/expect-decorator/ExpectApiPackage.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { test } from '@fixtures'
2-
import { getAuthDataFromStorageStateFile } from '@services/auth'
3-
import { SS_SYSADMIN_PATH } from '@services/storage-state'
4-
import { createRest, rGetPackageById } from '@services/rest'
5-
import { BASE_ORIGIN } from '@test-setup'
2+
import { createRestWithCredentials, rGetPackageById } from '@services/rest'
3+
import { BASE_URL } from '@test-setup'
64
import { BaseExpect } from './BaseExpect'
5+
import { SYSADMIN } from '@test-data'
76

8-
export type PackageInput = { packageId: string; name?: string }
7+
export interface PackageInput {
8+
packageId: string
9+
name?: string
10+
}
911

1012
export class ExpectApiPackage extends BaseExpect<PackageInput> {
1113

@@ -24,8 +26,7 @@ export class ExpectApiPackage extends BaseExpect<PackageInput> {
2426

2527
async toBeCreated(): Promise<void> {
2628
await test.step(this.formatStepMessage('to be created'), async () => {
27-
const authData = await getAuthDataFromStorageStateFile(SS_SYSADMIN_PATH)
28-
const rest = await createRest(BASE_ORIGIN, authData.token)
29+
const rest = await createRestWithCredentials(BASE_URL, SYSADMIN)
2930
const response = await rest.send(rGetPackageById, [200, 404], this.actual)
3031
const expectedStatus = 200
3132

src/services/expect-decorator/ExpectApiVersion.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { test } from '@fixtures'
2-
import { getAuthDataFromStorageStateFile } from '@services/auth'
3-
import { SS_SYSADMIN_PATH } from '@services/storage-state'
4-
import { createRest, rGetPackageVersion } from '@services/rest'
5-
import { BASE_ORIGIN } from '@test-setup'
2+
import { createRestWithCredentials, rGetPackageVersion } from '@services/rest'
3+
import { BASE_URL } from '@test-setup'
64
import { BaseExpect } from './BaseExpect'
5+
import { SYSADMIN } from '@test-data'
76

8-
export type VersionInput = { packageId: string; version: string }
7+
export interface VersionInput {
8+
packageId: string
9+
version: string
10+
}
911

1012
export class ExpectApiVersion extends BaseExpect<VersionInput> {
1113

@@ -24,8 +26,7 @@ export class ExpectApiVersion extends BaseExpect<VersionInput> {
2426

2527
async toBePublished(): Promise<void> {
2628
await test.step(this.formatStepMessage('to be created'), async () => {
27-
const authData = await getAuthDataFromStorageStateFile(SS_SYSADMIN_PATH)
28-
const rest = await createRest(BASE_ORIGIN, authData.token)
29+
const rest = await createRestWithCredentials(BASE_URL, SYSADMIN)
2930
const response = await rest.send(rGetPackageVersion, [200, 404], this.actual)
3031
const expectedStatus = 200
3132

0 commit comments

Comments
 (0)