Skip to content

Commit 122c7e2

Browse files
authored
Display all Api clients (#1481)
* copy to clipborad card * remove unsused packages * test for clipboard function * Notifications * notification adjust * test copytoclipboard coverage * test for client Keys * apikeys with secrets shown * wrap and design adjust * regenerate * userName * typing * rename * unit test for date converter * e2e test * client spa change
1 parent 8b20056 commit 122c7e2

File tree

19 files changed

+798
-131
lines changed

19 files changed

+798
-131
lines changed

apps/sensenet/cypress.config.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ export default defineConfig({
99

1010
env: {
1111
repoUrl: 'https://e2e-service.test.sensenet.com',
12-
identityServer: 'https://is.demo.sensenet.com',
12+
identityServer: 'https://is.test.sensenet.com',
1313
users: {
1414
admin: {
15-
clientId: 'xZSq7OvBQIPEXxWo',
16-
clientSecret: 'TSWPEcLvDY2xyGUgIGsclSK2vX6WKK4QbKuO6foxvlToIl5Ar1K79Vp15soJTvy4',
15+
clientId: 'emuRBycXurC1jhwD',
16+
clientSecret: '9xrLk56Scv12gu8WUlIx5WMYCMsv8HPblrGBKroNVCKYrMS1vKui8uTXhVGkV7O9',
1717
id: "/Root/IMS/Public('businesscat')",
1818
},
19-
developer: {
20-
clientId: 'devdog',
21-
clientSecret: '',
22-
id: "/Root/IMS/Public('devdog')",
19+
superAdmin: {
20+
clientId: '7bZk2drAheS3UPgV',
21+
clientSecret: '4UBzLkvPyWsWXrWUjy7QVkcVaDyLfjejiJ3FfDm3fU1LlTxaGI9OT4x1NJMxRCGu',
22+
id: "/Root/IMS/BuiltIn/Portal/('Admin')",
2323
},
2424
},
2525
},
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import { ApiKey, clientTypes, spaTypes } from '../../../src/components/settings/api-key'
2+
import { pathWithQueryParams } from '../../../src/services/query-string-builder'
3+
4+
function testApiKeySecrets(apiKey: ApiKey, index: number, isSPA = false) {
5+
const keysContainer = isSPA ? '[data-test="spa-keys"]' : '[data-test="client-keys"]'
6+
7+
if (apiKey.secrets.length === 0) {
8+
cy.get(keysContainer).children().eq(index).click().find('[data-test="secret-container"]').should('not.exist')
9+
return
10+
}
11+
12+
cy.get(keysContainer)
13+
.children()
14+
.eq(index)
15+
.find('[data-test="type-container"]')
16+
.click()
17+
.closest('[data-test="api-key-accordion-container"]')
18+
.find('[data-test="user-secret-container"]')
19+
.each(($key, secretIndex) => {
20+
cy.wrap($key)
21+
.should('exist')
22+
.click()
23+
.then(() => {
24+
cy.window().then((win) => {
25+
win.navigator.clipboard.readText().then((content) => {
26+
expect(content).to.equal(apiKey.secrets[secretIndex].value)
27+
})
28+
})
29+
})
30+
})
31+
}
32+
let clientKeys: ApiKey[] = []
33+
34+
let spaKeys: ApiKey[] = []
35+
36+
describe('Api Keys', () => {
37+
beforeEach(() => {
38+
cy.login('superAdmin')
39+
cy.visit(pathWithQueryParams({ path: '/', newParams: { repoUrl: Cypress.env('repoUrl') } }))
40+
})
41+
42+
it('should display spa APIKeys and its secret.', () => {
43+
cy.get('[data-test="drawer-menu-item-settings"]').click()
44+
cy.intercept(/GetClients/).as('getClients')
45+
cy.intercept(/GetClientsForRepository\?/).as('GetClientsForRepository')
46+
cy.get('[data-test="drawer-submenu-item-apikeys"]').click()
47+
48+
cy.wait('@getClients').then(({ response }) => {
49+
spaKeys = response?.body.clients.filter((client: any) => {
50+
return spaTypes.indexOf(client.type) > -1
51+
})
52+
53+
cy.get('[data-test="spa-keys"]').children().should('have.length', spaKeys.length)
54+
55+
for (let i = 0; i < spaKeys.length; i++) {
56+
const apiKey = spaKeys[i]
57+
58+
testApiKeySecrets(apiKey, i, true)
59+
}
60+
})
61+
62+
if (spaKeys.length === 0) {
63+
cy.reload()
64+
65+
cy.wait('@GetClientsForRepository').then(({ response: responseForRepo }) => {
66+
spaKeys = responseForRepo?.body.clients.filter((client: any) => {
67+
return spaTypes.indexOf(client.type) > -1
68+
})
69+
cy.get('[data-test="spa-keys"]').children().should('have.length', spaKeys.length)
70+
71+
for (let i = 0; i < spaKeys.length; i++) {
72+
const apiKey = spaKeys[i]
73+
74+
testApiKeySecrets(apiKey, i, true)
75+
}
76+
})
77+
}
78+
})
79+
80+
it('should copy spa APIKeys to clipboard', () => {
81+
cy.get('[data-test="drawer-menu-item-settings"]').click()
82+
cy.intercept(/GetClients\?/).as('getClients')
83+
cy.intercept(/GetClientsForRepository\?/).as('GetClientsForRepository')
84+
cy.get('[data-test="drawer-submenu-item-apikeys"]').click()
85+
86+
cy.wait('@getClients').then(({ response }) => {
87+
spaKeys = response?.body.clients.filter((client: any) => {
88+
return spaTypes.indexOf(client.type) > -1
89+
})
90+
91+
cy.get('[data-test="spa-keys"]')
92+
.children()
93+
.should('have.length', spaKeys.length)
94+
.each(($key, index) => {
95+
cy.wrap($key)
96+
.should('exist')
97+
.click()
98+
.then(() => {
99+
cy.window().then((win) => {
100+
win.navigator.clipboard.readText().then((content) => {
101+
expect(content).to.equal(spaKeys[index].clientId)
102+
})
103+
})
104+
})
105+
})
106+
107+
if (spaKeys.length === 0) {
108+
cy.reload()
109+
cy.wait('@GetClientsForRepository').then(({ response: reponseForRepo }) => {
110+
spaKeys = reponseForRepo?.body.clients.filter((client: any) => {
111+
return spaTypes.indexOf(client.type) > -1
112+
})
113+
114+
cy.get('[data-test="spa-keys"]')
115+
.children()
116+
.should('have.length', spaKeys.length)
117+
.each(($key, index) => {
118+
cy.wrap($key)
119+
.should('exist')
120+
.click()
121+
.then(() => {
122+
cy.window().then((win) => {
123+
win.navigator.clipboard.readText().then((content) => {
124+
expect(content).to.equal(spaKeys[index].clientId)
125+
})
126+
})
127+
})
128+
})
129+
})
130+
}
131+
})
132+
})
133+
134+
it('should display client APIKeys', () => {
135+
cy.get('[data-test="drawer-menu-item-settings"]').click()
136+
cy.intercept(/GetClients/).as('getClients')
137+
cy.intercept(/GetClientsForRepository\?/).as('GetClientsForRepository')
138+
cy.get('[data-test="drawer-submenu-item-apikeys"]').click()
139+
140+
cy.wait('@getClients').then(({ response }) => {
141+
cy.get('[data-test="clients-tab"]').click()
142+
clientKeys = response?.body.clients.filter((client: any) => {
143+
return clientTypes.indexOf(client.type) > -1
144+
})
145+
146+
cy.get('[data-test="client-keys"]').children().should('have.length', clientKeys.length)
147+
148+
for (let i = 0; i < clientKeys.length; i++) {
149+
const apiKey = clientKeys[i]
150+
testApiKeySecrets(apiKey, i)
151+
}
152+
153+
if (clientKeys.length === 0) {
154+
cy.reload()
155+
cy.get('[data-test="clients-tab"]').click()
156+
cy.wait('@GetClientsForRepository').then(({ response: responseForRepo }) => {
157+
clientKeys = responseForRepo?.body.clients.filter((client: any) => {
158+
return clientTypes.indexOf(client.type) > -1
159+
})
160+
161+
cy.get('[data-test="client-keys"]').children().should('have.length', clientKeys.length)
162+
163+
for (let i = 0; i < clientKeys.length; i++) {
164+
const apiKey = clientKeys[i]
165+
166+
testApiKeySecrets(apiKey, i)
167+
}
168+
})
169+
}
170+
})
171+
})
172+
173+
it('should copy client APIKeys to clipboard', () => {
174+
cy.get('[data-test="drawer-menu-item-settings"]').click()
175+
cy.intercept(/GetClients\?/).as('getClients')
176+
cy.intercept(/GetClientsForRepository\?/).as('GetClientsForRepository')
177+
cy.get('[data-test="drawer-submenu-item-apikeys"]').click()
178+
179+
cy.wait('@getClients').then(({ response }) => {
180+
cy.get('[data-test="clients-tab"]').click()
181+
182+
clientKeys = response?.body.clients.filter((client: any) => {
183+
return clientTypes.indexOf(client.type) > -1
184+
})
185+
186+
cy.get('[data-test="client-keys"]')
187+
.children()
188+
.should('have.length', clientKeys.length)
189+
.each(($key, index) => {
190+
cy.wrap($key)
191+
.should('exist')
192+
.click()
193+
.then(() => {
194+
cy.window().then((win) => {
195+
win.navigator.clipboard.readText().then((content) => {
196+
expect(content).to.equal(clientKeys[index].clientId)
197+
})
198+
})
199+
})
200+
})
201+
})
202+
203+
if (clientKeys.length === 0) {
204+
cy.reload()
205+
cy.get('[data-test="clients-tab"]').click()
206+
cy.wait('@GetClientsForRepository').then(({ response }) => {
207+
clientKeys = response?.body.clients.filter((client: any) => {
208+
return clientTypes.indexOf(client.type) > -1
209+
})
210+
211+
cy.get('[data-test="client-keys"]')
212+
.children()
213+
.should('have.length', clientKeys.length)
214+
.each(($key, index) => {
215+
cy.wrap($key)
216+
.should('exist')
217+
.click()
218+
.then(() => {
219+
cy.window().then((win) => {
220+
win.navigator.clipboard.readText().then((content) => {
221+
expect(content).to.equal(clientKeys[index].clientId)
222+
})
223+
})
224+
})
225+
})
226+
})
227+
}
228+
})
229+
})

apps/sensenet/cypress/support/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/// <reference types="cypress-xpath/src" />
33

44
declare namespace Cypress {
5-
type UserTypes = 'developer' | 'admin'
5+
type UserTypes = 'developer' | 'admin' | 'superAdmin'
66

77
interface Chainable {
88
/**

apps/sensenet/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"build:stats": "webpack --config webpack.prod.js --profile --json > stats.json",
2020
"start": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack serve --progress --config webpack.dev.js",
2121
"cypress": "cypress open --env coverage=false",
22+
"cypress:local": "cypress open --env coverage=false --config baseUrl=http://localhost:8080",
2223
"cypress:all": "cypress run --env coverage=false",
2324
"cypress:coverage": "cypress run --headed -b chrome --config-file ./cypress.config.ts && nyc report --reporter=text-summary",
2425
"instrument": "nyc instrument --compct=false --complete-copy --delete src instrumented && yarn start --env coverage"

apps/sensenet/src/application-paths.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const PATHS = {
1616
localization: { appPath: '/settings/localization/:action?', snPath: '/Root/Localization' },
1717
webhooks: { appPath: '/settings/webhooks/:action?', snPath: '/Root/System/WebHooks' },
1818
settings: { appPath: '/settings/:submenu?' },
19+
apiKeys: { appPath: '/settings/apikeys' },
1920
} as const
2021

2122
type SettingsItemType = 'stats' | 'apikeys' | 'webhooks' | 'adminui'

apps/sensenet/src/components/CopyPath.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Tooltip } from '@material-ui/core'
22
import Button from '@material-ui/core/Button'
33
import CheckCircleOutlineOutlinedIcon from '@material-ui/icons/CheckCircleOutlineOutlined'
44
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined'
5+
import { copyToClipboard } from '@sensenet/client-utils'
56
import React, { useState } from 'react'
67
import { useLocalization } from '../hooks'
78

@@ -14,7 +15,7 @@ const CopyPath = ({ copyText }: Props) => {
1415

1516
const copyTextToClipboard = async (text: string) => {
1617
if ('clipboard' in navigator) {
17-
return await navigator.clipboard.writeText(text)
18+
return copyToClipboard(text)
1819
}
1920

2021
return document.execCommand('copy', true, text)

apps/sensenet/src/components/NotificationComponent.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const getItemTextColor = (item: LeveledLogEntry<any>) => {
3737
export const getAutoHideDuration = (item: LeveledLogEntry<any>) => {
3838
switch (item.level) {
3939
case LogLevel.Error:
40+
return 5000
4041
case LogLevel.Fatal:
4142
return undefined
4243
case LogLevel.Warning:

apps/sensenet/src/components/settings/api-key.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ export interface Secret {
55
validTill: string
66
}
77

8-
export enum ApiKeyType {
9-
ExternalClient = 'ExternalClient',
10-
ExternalSpa = 'ExternalSpa',
11-
}
8+
export type ClientType = 'ExternalClient' | 'InternalClient'
9+
10+
export type SpaType = 'InternalSpa' | 'ExternalSpa' | 'AdminUi'
11+
12+
export const spaTypes: SpaType[] = ['ExternalSpa', 'InternalSpa', 'AdminUi']
13+
export const clientTypes: ClientType[] = ['ExternalClient', 'InternalClient']
1214

1315
export interface ApiKey {
1416
name: string
1517
repository: string
1618
clientId: string
1719
userName: string
1820
authority: string
19-
type: ApiKeyType
21+
type: SpaType | ClientType
2022
secrets: Secret[]
2123
}

0 commit comments

Comments
 (0)