mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-08 19:27:41 +00:00
test(e2e): test authorizations (#4342)
* add specs that cover the b2b demo * update cypress * test handling manager roles * use shared mocha contexts * use beforeEach instead of before * improve readability * improve application test * remove static waits * remove old awaitDesired * test owned project authorizations * simplify ensure.ts * test granted projects authz * disable prevSubject for shouldNotExist * await non-existence, then expect no error * update dependencies * fix tests from scratch * fix settings tests from scratch * Apply suggestions from code review Co-authored-by: Max Peintner <max@caos.ch> * Implement code review suggestions * use spread operator * settings properties must match * add check settings object * revert spread operator Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
parent
6daf44a34a
commit
51febd7e4e
@ -142,7 +142,7 @@ goreleaser build --id dev --snapshot --single-target --rm-dist --output .artifac
|
|||||||
> For speeding up rebuilds, you can reexecute only specific steps you think are necessary based on your changes.
|
> For speeding up rebuilds, you can reexecute only specific steps you think are necessary based on your changes.
|
||||||
> Generating gRPC stubs: `DOCKER_BUILDKIT=1 docker build -f build/zitadel/Dockerfile . --target go-copy -o .`
|
> Generating gRPC stubs: `DOCKER_BUILDKIT=1 docker build -f build/zitadel/Dockerfile . --target go-copy -o .`
|
||||||
> Running unit tests: `DOCKER_BUILDKIT=1 docker build -f build/zitadel/Dockerfile . --target go-codecov`
|
> Running unit tests: `DOCKER_BUILDKIT=1 docker build -f build/zitadel/Dockerfile . --target go-codecov`
|
||||||
> Generating the console: `DOCKER_BUILDKIT=1 docker build -f build/console/Dockerfile . -t zitadel-npm-console --target angular-export -o internal/api/ui/console/static/`
|
> Generating the console: `DOCKER_BUILDKIT=1 docker build -f build/console/Dockerfile . --target angular-export -o internal/api/ui/console/static/`
|
||||||
> Build the binary: `goreleaser build --id dev --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel --skip-before`
|
> Build the binary: `goreleaser build --id dev --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel --skip-before`
|
||||||
|
|
||||||
You can now run and debug the binary in .artifacts/zitadel/zitadel using your favourite IDE, for example GoLand.
|
You can now run and debug the binary in .artifacts/zitadel/zitadel using your favourite IDE, for example GoLand.
|
||||||
|
@ -7,7 +7,7 @@ export class CopyToClipboardDirective {
|
|||||||
@Input() valueToCopy: string = '';
|
@Input() valueToCopy: string = '';
|
||||||
@Output() copiedValue: EventEmitter<string> = new EventEmitter();
|
@Output() copiedValue: EventEmitter<string> = new EventEmitter();
|
||||||
|
|
||||||
@HostListener('click', ['$event']) onMouseEnter($event: any): void {
|
@HostListener('click', ['$event']) onClick($event: any): void {
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
this.copytoclipboard(this.valueToCopy);
|
this.copytoclipboard(this.valueToCopy);
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
>
|
>
|
||||||
<div class="role-cb-content">
|
<div class="role-cb-content">
|
||||||
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
|
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
|
||||||
<span>{{ role | roletransform }}</span>
|
<span data-e2e="role-checkbox">{{ role | roletransform }}</span>
|
||||||
<i class="info-hover las la-question-circle" matTooltip="{{ 'MEMBERROLES.' + role | translate }}"></i>
|
<i class="info-hover las la-question-circle" matTooltip="{{ 'MEMBERROLES.' + role | translate }}"></i>
|
||||||
</div>
|
</div>
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
@ -61,6 +61,7 @@
|
|||||||
mat-raised-button
|
mat-raised-button
|
||||||
class="ok-button"
|
class="ok-button"
|
||||||
(click)="closeDialogWithSuccess()"
|
(click)="closeDialogWithSuccess()"
|
||||||
|
data-e2e="confirm-add-member-button"
|
||||||
>
|
>
|
||||||
{{ 'ACTIONS.ADD' | translate }}
|
{{ 'ACTIONS.ADD' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
class="contributor-avatar-circle"
|
class="contributor-avatar-circle"
|
||||||
matTooltip="{{ member.displayName }} | {{ member.rolesList | roletransform }}"
|
matTooltip="{{ member.displayName }} | {{ member.rolesList | roletransform }}"
|
||||||
[ngStyle]="{ 'z-index': 20 - i }"
|
[ngStyle]="{ 'z-index': 20 - i }"
|
||||||
|
data-e2e="member-avatar"
|
||||||
>
|
>
|
||||||
<cnsl-avatar
|
<cnsl-avatar
|
||||||
*ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
|
*ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
|
||||||
@ -40,6 +41,7 @@
|
|||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
aria-label="Add member"
|
aria-label="Add member"
|
||||||
|
data-e2e="add-member-button"
|
||||||
>
|
>
|
||||||
<mat-icon>add</mat-icon>
|
<mat-icon>add</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
cnslCopyToClipboard
|
cnslCopyToClipboard
|
||||||
[valueToCopy]="login"
|
[valueToCopy]="login"
|
||||||
(copiedValue)="copied = $event"
|
(copiedValue)="copied = $event"
|
||||||
|
data-e2e="copy-loginname"
|
||||||
>
|
>
|
||||||
{{ login }}
|
{{ login }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -93,6 +93,7 @@
|
|||||||
(click)="$event.stopPropagation(); triggerDeleteMember(member)"
|
(click)="$event.stopPropagation(); triggerDeleteMember(member)"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
[disabled]="canDelete === false"
|
[disabled]="canDelete === false"
|
||||||
|
data-e2e="remove-member-button"
|
||||||
>
|
>
|
||||||
<i class="las la-trash"></i>
|
<i class="las la-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -114,10 +115,11 @@
|
|||||||
[removable]="canWrite"
|
[removable]="canWrite"
|
||||||
[selectable]="false"
|
[selectable]="false"
|
||||||
(removed)="removeRole(member, role)"
|
(removed)="removeRole(member, role)"
|
||||||
|
data-e2e="role"
|
||||||
>
|
>
|
||||||
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
|
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
|
||||||
<span>{{ role | roletransform }}</span>
|
<span>{{ role | roletransform }}</span>
|
||||||
<button *ngIf="canWrite" matChipRemove>
|
<button *ngIf="canWrite" matChipRemove data-e2e="remove-role-button">
|
||||||
<mat-icon>cancel</mat-icon>
|
<mat-icon>cancel</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-chip>
|
</mat-chip>
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
[formControl]="myControl"
|
[formControl]="myControl"
|
||||||
placeholder="johndoe@domain.com"
|
placeholder="johndoe@domain.com"
|
||||||
[matAutocomplete]="auto"
|
[matAutocomplete]="auto"
|
||||||
|
data-e2e="add-member-input"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [displayWith]="displayFn">
|
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [displayWith]="displayFn">
|
||||||
@ -72,7 +73,7 @@
|
|||||||
<mat-spinner diameter="30"></mat-spinner>
|
<mat-spinner diameter="30"></mat-spinner>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-option *ngFor="let user of filteredUsers" [value]="user">
|
<mat-option *ngFor="let user of filteredUsers" [value]="user">
|
||||||
<div class="user-option">
|
<div class="user-option" data-e2e="user-option">
|
||||||
<div class="circle">
|
<div class="circle">
|
||||||
<cnsl-avatar
|
<cnsl-avatar
|
||||||
*ngIf="
|
*ngIf="
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"
|
"
|
||||||
class="sidenav-setting-list-element hide-on-mobile"
|
class="sidenav-setting-list-element hide-on-mobile"
|
||||||
[ngClass]="{ active: currentSetting === setting.id, show: currentSetting === undefined }"
|
[ngClass]="{ active: currentSetting === setting.id, show: currentSetting === undefined }"
|
||||||
|
[attr.data-e2e]="'sidenav-element-' + setting.id"
|
||||||
>
|
>
|
||||||
<span>{{ setting.i18nKey | translate }}</span>
|
<span>{{ setting.i18nKey | translate }}</span>
|
||||||
<mat-icon *ngIf="setting.showWarn" class="warn-icon" svgIcon="mdi_shield_alert"></mat-icon>
|
<mat-icon *ngIf="setting.showWarn" class="warn-icon" svgIcon="mdi_shield_alert"></mat-icon>
|
||||||
|
@ -58,8 +58,10 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
|||||||
breadcrumbService.setBreadcrumb([bread]);
|
breadcrumbService.setBreadcrumb([bread]);
|
||||||
|
|
||||||
auth.activeOrgChanged.pipe(takeUntil(this.destroy$)).subscribe((org) => {
|
auth.activeOrgChanged.pipe(takeUntil(this.destroy$)).subscribe((org) => {
|
||||||
|
if (this.org && org) {
|
||||||
this.getData();
|
this.getData();
|
||||||
this.loadMetadata();
|
this.loadMetadata();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +117,7 @@
|
|||||||
[urisList]="oidcAppRequest.toObject().redirectUrisList"
|
[urisList]="oidcAppRequest.toObject().redirectUrisList"
|
||||||
[getValues]="requestRedirectValuesSubject$"
|
[getValues]="requestRedirectValuesSubject$"
|
||||||
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
|
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
|
||||||
|
data-e2e="redirect-uris"
|
||||||
>
|
>
|
||||||
</cnsl-redirect-uris>
|
</cnsl-redirect-uris>
|
||||||
|
|
||||||
@ -145,6 +146,7 @@
|
|||||||
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
|
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
|
||||||
[getValues]="requestRedirectValuesSubject$"
|
[getValues]="requestRedirectValuesSubject$"
|
||||||
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
[isNative]="appType?.value.oidcAppType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
|
||||||
|
data-e2e="postlogout-uris"
|
||||||
>
|
>
|
||||||
</cnsl-redirect-uris>
|
</cnsl-redirect-uris>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<p class="desc cnsl-secondary-text">{{ 'APP.OIDC.CLIENTSECRET_DESCRIPTION' | translate }}</p>
|
<p class="desc cnsl-secondary-text">{{ 'APP.OIDC.CLIENTSECRET_DESCRIPTION' | translate }}</p>
|
||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<div class="flex" *ngIf="data.clientId">
|
<div class="flex" *ngIf="data.clientId">
|
||||||
<span class="overflow-auto"><span class="desc">ClientId:</span> {{ data.clientId }}</span>
|
<span class="overflow-auto" data-e2e="client-id"><span class="desc">ClientId:</span> {{ data.clientId }}</span>
|
||||||
<button
|
<button
|
||||||
color="primary"
|
color="primary"
|
||||||
[disabled]="copied === data.clientId"
|
[disabled]="copied === data.clientId"
|
||||||
@ -13,6 +13,7 @@
|
|||||||
[valueToCopy]="data.clientId"
|
[valueToCopy]="data.clientId"
|
||||||
(copiedValue)="this.copied = $event"
|
(copiedValue)="this.copied = $event"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
|
data-e2e="client-id-copy"
|
||||||
>
|
>
|
||||||
<i *ngIf="copied !== data.clientId" class="las la-clipboard"></i>
|
<i *ngIf="copied !== data.clientId" class="las la-clipboard"></i>
|
||||||
<i *ngIf="copied === data.clientId" class="las la-clipboard-check"></i>
|
<i *ngIf="copied === data.clientId" class="las la-clipboard-check"></i>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<form class="redirect-uris-form" (ngSubmit)="add(redInput)" data-e2e="redirect-uris">
|
<form class="redirect-uris-form" (ngSubmit)="add(redInput)">
|
||||||
<cnsl-form-field class="formfield">
|
<cnsl-form-field class="formfield">
|
||||||
<cnsl-label>{{ title }}</cnsl-label>
|
<cnsl-label>{{ title }}</cnsl-label>
|
||||||
|
|
||||||
|
@ -5,37 +5,43 @@ describe('applications', () => {
|
|||||||
const testProjectName = 'e2eprojectapplication';
|
const testProjectName = 'e2eprojectapplication';
|
||||||
const testAppName = 'e2eappundertest';
|
const testAppName = 'e2eappundertest';
|
||||||
|
|
||||||
beforeEach(`ensure it doesn't exist already`, () => {
|
beforeEach(() => {
|
||||||
apiAuth().then((api) => {
|
apiAuth()
|
||||||
ensureProjectExists(api, testProjectName).then((projectID) => {
|
.as('api')
|
||||||
ensureProjectResourceDoesntExist(api, projectID, Apps, testAppName).then(() => {
|
.then((api) => {
|
||||||
cy.visit(`/projects/${projectID}`);
|
ensureProjectExists(api, testProjectName).as('projectId');
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('add app', function () {
|
||||||
|
beforeEach(`ensure it doesn't exist already`, function () {
|
||||||
|
ensureProjectResourceDoesntExist(this.api, this.projectId, Apps, testAppName);
|
||||||
|
cy.visit(`/projects/${this.projectId}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add app', () => {
|
it('add app', () => {
|
||||||
cy.get('[data-e2e="app-card-add"]').should('be.visible').click();
|
cy.get('[data-e2e="app-card-add"]').should('be.visible').click();
|
||||||
// select webapp
|
cy.get('[formcontrolname="name"]').focus().type(testAppName);
|
||||||
cy.get('[formcontrolname="name"]').type(testAppName);
|
|
||||||
cy.get('[for="WEB"]').click();
|
cy.get('[for="WEB"]').click();
|
||||||
cy.get('[data-e2e="continue-button-nameandtype"]').click();
|
cy.get('[data-e2e="continue-button-nameandtype"]').click();
|
||||||
//select authentication
|
cy.get('[for="PKCE"]').should('be.visible').click();
|
||||||
cy.get('[for="PKCE"]').click();
|
|
||||||
cy.get('[data-e2e="continue-button-authmethod"]').click();
|
cy.get('[data-e2e="continue-button-authmethod"]').click();
|
||||||
//enter URL
|
cy.get('[data-e2e="redirect-uris"] input').focus().type('http://localhost:3000/api/auth/callback/zitadel');
|
||||||
cy.get('cnsl-redirect-uris').eq(0).type('https://testurl.org');
|
cy.get('[data-e2e="postlogout-uris"] input').focus().type('http://localhost:3000');
|
||||||
cy.get('cnsl-redirect-uris').eq(1).type('https://testlogouturl.org');
|
|
||||||
cy.get('[data-e2e="continue-button-redirecturis"]').click();
|
cy.get('[data-e2e="continue-button-redirecturis"]').click();
|
||||||
cy.get('[data-e2e="create-button"]')
|
cy.get('[data-e2e="create-button"]').click();
|
||||||
.click()
|
|
||||||
.then(() => {
|
|
||||||
cy.get('[id*=overlay]').should('exist');
|
cy.get('[id*=overlay]').should('exist');
|
||||||
});
|
|
||||||
cy.get('.data-e2e-success');
|
cy.get('.data-e2e-success');
|
||||||
cy.wait(200);
|
const expectClientId = new RegExp(`^.*[0-9]+\\@${testProjectName}.*$`);
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
cy.get('[data-e2e="client-id-copy"]').click();
|
||||||
//TODO: check client ID/Secret
|
cy.contains('[data-e2e="client-id"]', expectClientId);
|
||||||
|
cy.clipboardMatches(expectClientId);
|
||||||
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('edit app', () => {
|
||||||
|
it('should configure an application to enable dev mode');
|
||||||
|
it('should configure an application to put user roles and info inside id token');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,19 +7,20 @@ describe('humans', () => {
|
|||||||
const testHumanUserNameAdd = 'e2ehumanusernameadd';
|
const testHumanUserNameAdd = 'e2ehumanusernameadd';
|
||||||
const testHumanUserNameRemove = 'e2ehumanusernameremove';
|
const testHumanUserNameRemove = 'e2ehumanusernameremove';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
apiAuth().as('api');
|
||||||
|
});
|
||||||
|
|
||||||
describe('add', () => {
|
describe('add', () => {
|
||||||
before(`ensure it doesn't exist already`, () => {
|
beforeEach(`ensure it doesn't exist already`, function () {
|
||||||
apiAuth().then((apiCallProperties) => {
|
ensureUserDoesntExist(this.api, loginname(testHumanUserNameAdd, Cypress.env('ORGANIZATION')));
|
||||||
ensureUserDoesntExist(apiCallProperties, testHumanUserNameAdd).then(() => {
|
|
||||||
cy.visit(humansPath);
|
cy.visit(humansPath);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add a user', () => {
|
it('should add a user', () => {
|
||||||
cy.get('[data-e2e="create-user-button"]').click();
|
cy.get('[data-e2e="create-user-button"]').click();
|
||||||
cy.url().should('contain', 'users/create');
|
cy.url().should('contain', 'users/create');
|
||||||
cy.get('[formcontrolname="email"]').type(loginname('e2ehuman', Cypress.env('ORGANIZATION')));
|
cy.get('[formcontrolname="email"]').type('dummy@dummy.com');
|
||||||
//force needed due to the prefilled username prefix
|
//force needed due to the prefilled username prefix
|
||||||
cy.get('[formcontrolname="userName"]').type(loginname(testHumanUserNameAdd, Cypress.env('ORGANIZATION')));
|
cy.get('[formcontrolname="userName"]').type(loginname(testHumanUserNameAdd, Cypress.env('ORGANIZATION')));
|
||||||
cy.get('[formcontrolname="firstName"]').type('e2ehumanfirstname');
|
cy.get('[formcontrolname="firstName"]').type('e2ehumanfirstname');
|
||||||
@ -27,33 +28,29 @@ describe('humans', () => {
|
|||||||
cy.get('[formcontrolname="phone"]').type('+41 123456789');
|
cy.get('[formcontrolname="phone"]').type('+41 123456789');
|
||||||
cy.get('[data-e2e="create-button"]').click();
|
cy.get('[data-e2e="create-button"]').click();
|
||||||
cy.get('.data-e2e-success');
|
cy.get('.data-e2e-success');
|
||||||
cy.wait(200);
|
const loginName = loginname(testHumanUserNameAdd, Cypress.env('ORGANIZATION'));
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
cy.contains('[data-e2e="copy-loginname"]', loginName).click();
|
||||||
|
cy.clipboardMatches(loginName);
|
||||||
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('remove', () => {
|
describe('remove', () => {
|
||||||
before('ensure it exists', () => {
|
beforeEach('ensure it exists', function () {
|
||||||
apiAuth().then((api) => {
|
ensureHumanUserExists(this.api, loginname(testHumanUserNameRemove, Cypress.env('ORGANIZATION')));
|
||||||
ensureHumanUserExists(api, loginname(testHumanUserNameRemove, Cypress.env('ORGANIZATION'))).then(() => {
|
|
||||||
cy.visit(humansPath);
|
cy.visit(humansPath);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete a human user', () => {
|
it('should delete a human user', () => {
|
||||||
cy.contains('tr', testHumanUserNameRemove)
|
const rowSelector = `tr:contains(${testHumanUserNameRemove})`;
|
||||||
// doesn't work, need to force click.
|
cy.get(rowSelector).find('[data-e2e="enabled-delete-button"]').click({ force: true });
|
||||||
// .trigger('mouseover')
|
|
||||||
.find('[data-e2e="enabled-delete-button"]')
|
|
||||||
.click({ force: true });
|
|
||||||
cy.get('[data-e2e="confirm-dialog-input"]')
|
cy.get('[data-e2e="confirm-dialog-input"]')
|
||||||
.focus()
|
.focus()
|
||||||
.type(loginname(testHumanUserNameRemove, Cypress.env('ORGANIZATION')));
|
.type(loginname(testHumanUserNameRemove, Cypress.env('ORGANIZATION')));
|
||||||
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
||||||
cy.get('.data-e2e-success');
|
cy.get('.data-e2e-success');
|
||||||
cy.wait(200);
|
cy.shouldNotExist({ selector: rowSelector, timeout: 2000 });
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,18 +3,19 @@ import { ensureMachineUserExists, ensureUserDoesntExist } from '../../support/ap
|
|||||||
import { loginname } from '../../support/login/users';
|
import { loginname } from '../../support/login/users';
|
||||||
|
|
||||||
describe('machines', () => {
|
describe('machines', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apiAuth().as('api');
|
||||||
|
});
|
||||||
|
|
||||||
const machinesPath = `/users?type=machine`;
|
const machinesPath = `/users?type=machine`;
|
||||||
const testMachineUserNameAdd = 'e2emachineusernameadd';
|
const testMachineUserNameAdd = 'e2emachineusernameadd';
|
||||||
const testMachineUserNameRemove = 'e2emachineusernameremove';
|
const testMachineUserNameRemove = 'e2emachineusernameremove';
|
||||||
|
|
||||||
describe('add', () => {
|
describe('add', () => {
|
||||||
before(`ensure it doesn't exist already`, () => {
|
beforeEach(`ensure it doesn't exist already`, function () {
|
||||||
apiAuth().then((apiCallProperties) => {
|
ensureUserDoesntExist(this.api, testMachineUserNameAdd);
|
||||||
ensureUserDoesntExist(apiCallProperties, testMachineUserNameAdd).then(() => {
|
|
||||||
cy.visit(machinesPath);
|
cy.visit(machinesPath);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add a machine', () => {
|
it('should add a machine', () => {
|
||||||
cy.get('[data-e2e="create-user-button"]').click();
|
cy.get('[data-e2e="create-user-button"]').click();
|
||||||
@ -25,31 +26,28 @@ describe('machines', () => {
|
|||||||
cy.get('[formcontrolname="description"]').type('e2emachinedescription');
|
cy.get('[formcontrolname="description"]').type('e2emachinedescription');
|
||||||
cy.get('[data-e2e="create-button"]').click();
|
cy.get('[data-e2e="create-button"]').click();
|
||||||
cy.get('.data-e2e-success');
|
cy.get('.data-e2e-success');
|
||||||
cy.wait(200);
|
cy.contains('[data-e2e="copy-loginname"]', testMachineUserNameAdd).click();
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
cy.clipboardMatches(testMachineUserNameAdd);
|
||||||
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('remove', () => {
|
describe('edit', () => {
|
||||||
before('ensure it exists', () => {
|
beforeEach('ensure it exists', function () {
|
||||||
apiAuth().then((api) => {
|
ensureMachineUserExists(this.api, testMachineUserNameRemove);
|
||||||
ensureMachineUserExists(api, testMachineUserNameRemove).then(() => {
|
|
||||||
cy.visit(machinesPath);
|
cy.visit(machinesPath);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete a machine', () => {
|
it('should delete a machine', () => {
|
||||||
cy.contains('tr', testMachineUserNameRemove)
|
const rowSelector = `tr:contains(${testMachineUserNameRemove})`;
|
||||||
// doesn't work, need to force click.
|
cy.get(rowSelector).find('[data-e2e="enabled-delete-button"]').click({ force: true });
|
||||||
// .trigger('mouseover')
|
|
||||||
.find('[data-e2e="enabled-delete-button"]')
|
|
||||||
.click({ force: true });
|
|
||||||
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testMachineUserNameRemove);
|
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testMachineUserNameRemove);
|
||||||
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
||||||
cy.get('.data-e2e-success');
|
cy.get('.data-e2e-success');
|
||||||
cy.wait(200);
|
cy.shouldNotExist({ selector: rowSelector, timeout: 2000 });
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create a personal access token');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
6
e2e/cypress/e2e/organization/organizations.cy.ts
Normal file
6
e2e/cypress/e2e/organization/organizations.cy.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
describe('organizations', () => {
|
||||||
|
it('should add an organization with the personal account as org owner');
|
||||||
|
describe('changing the current organization', () => {
|
||||||
|
it('should update displayed organization details');
|
||||||
|
});
|
||||||
|
});
|
@ -1,110 +1,249 @@
|
|||||||
|
import { ensureProjectGrantExists } from 'support/api/grants';
|
||||||
|
import {
|
||||||
|
ensureHumanIsOrgMember,
|
||||||
|
ensureHumanIsNotOrgMember,
|
||||||
|
ensureHumanIsNotProjectMember,
|
||||||
|
ensureHumanIsProjectMember,
|
||||||
|
} from 'support/api/members';
|
||||||
|
import { ensureOrgExists } from 'support/api/orgs';
|
||||||
|
import { ensureHumanUserExists, ensureUserDoesntExist } from 'support/api/users';
|
||||||
|
import { loginname } from 'support/login/users';
|
||||||
import { apiAuth } from '../../support/api/apiauth';
|
import { apiAuth } from '../../support/api/apiauth';
|
||||||
import { ensureProjectExists, ensureProjectResourceDoesntExist, Roles } from '../../support/api/projects';
|
import { ensureProjectExists, ensureProjectResourceDoesntExist, Roles } from '../../support/api/projects';
|
||||||
|
|
||||||
describe('permissions', () => {
|
describe('permissions', () => {
|
||||||
const testProjectName = 'e2eprojectpermission';
|
beforeEach(() => {
|
||||||
const testAppName = 'e2eapppermission';
|
apiAuth().as('api');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('management', () => {
|
||||||
|
const testManagerLoginname = loginname('e2ehumanmanager', Cypress.env('ORGANIZATION'));
|
||||||
|
function testAuthorizations(
|
||||||
|
roles: string[],
|
||||||
|
beforeCreate: Mocha.HookFunction,
|
||||||
|
beforeMutate: Mocha.HookFunction,
|
||||||
|
navigate: Mocha.HookFunction,
|
||||||
|
) {
|
||||||
|
beforeEach(function () {
|
||||||
|
ensureUserDoesntExist(this.api, testManagerLoginname);
|
||||||
|
ensureHumanUserExists(this.api, testManagerLoginname);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create authorization', () => {
|
||||||
|
beforeEach(beforeCreate);
|
||||||
|
beforeEach(navigate);
|
||||||
|
|
||||||
|
it('should add a manager', () => {
|
||||||
|
cy.get('[data-e2e="add-member-button"]').click();
|
||||||
|
cy.get('[data-e2e="add-member-input"]').type(testManagerLoginname);
|
||||||
|
cy.get('[data-e2e="user-option"]').click();
|
||||||
|
cy.contains('[data-e2e="role-checkbox"]', roles[0]).click();
|
||||||
|
cy.get('[data-e2e="confirm-add-member-button"]').click();
|
||||||
|
cy.get('.data-e2e-success');
|
||||||
|
cy.contains('[data-e2e="member-avatar"]', 'ee');
|
||||||
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mutate authorization', () => {
|
||||||
|
const rowSelector = `tr:contains(${testManagerLoginname})`;
|
||||||
|
|
||||||
|
beforeEach(beforeMutate);
|
||||||
|
beforeEach(navigate);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.contains('[data-e2e="member-avatar"]', 'ee').click();
|
||||||
|
cy.get(rowSelector).as('managerRow');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove a manager', () => {
|
||||||
|
cy.get('@managerRow').find('[data-e2e="remove-member-button"]').click({ force: true });
|
||||||
|
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
||||||
|
cy.get('.data-e2e-success');
|
||||||
|
cy.shouldNotExist({ selector: rowSelector, timeout: 2000 });
|
||||||
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove a managers authorization', () => {
|
||||||
|
cy.get('@managerRow').find('[data-e2e="role"]').should('have.length', roles.length);
|
||||||
|
cy.get('@managerRow')
|
||||||
|
.contains('[data-e2e="role"]', roles[0])
|
||||||
|
.find('[data-e2e="remove-role-button"]')
|
||||||
|
.click({ force: true }); // TODO: Is this a bug?
|
||||||
|
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
||||||
|
cy.get('.data-e2e-success');
|
||||||
|
cy.get('@managerRow')
|
||||||
|
.find('[data-e2e="remove-role-button"]')
|
||||||
|
.should('have.length', roles.length - 1);
|
||||||
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('organizations', () => {
|
||||||
|
const roles = [
|
||||||
|
{ internal: 'ORG_OWNER', display: 'Org Owner' },
|
||||||
|
{ internal: 'ORG_OWNER_VIEWER', display: 'Org Owner Viewer' },
|
||||||
|
];
|
||||||
|
|
||||||
|
testAuthorizations(
|
||||||
|
roles.map((role) => role.display),
|
||||||
|
function () {
|
||||||
|
ensureHumanIsNotOrgMember(this.api, testManagerLoginname);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
ensureHumanIsNotOrgMember(this.api, testManagerLoginname);
|
||||||
|
ensureHumanIsOrgMember(
|
||||||
|
this.api,
|
||||||
|
testManagerLoginname,
|
||||||
|
roles.map((role) => role.internal),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
cy.visit('/orgs');
|
||||||
|
cy.contains('tr', Cypress.env('ORGANIZATION')).click();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('projects', () => {
|
||||||
|
describe('owned projects', () => {
|
||||||
|
beforeEach(function () {
|
||||||
|
ensureProjectExists(this.api, 'e2eprojectpermission').as('projectId');
|
||||||
|
});
|
||||||
|
|
||||||
|
const visitOwnedProject: Mocha.HookFunction = function () {
|
||||||
|
cy.visit(`/projects/${this.projectId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('authorizations', () => {
|
||||||
|
const roles = [
|
||||||
|
{ internal: 'PROJECT_OWNER_GLOBAL', display: 'Project Owner Global' },
|
||||||
|
{ internal: 'PROJECT_OWNER_VIEWER_GLOBAL', display: 'Project Owner Viewer Global' },
|
||||||
|
];
|
||||||
|
|
||||||
|
testAuthorizations(
|
||||||
|
roles.map((role) => role.display),
|
||||||
|
function () {
|
||||||
|
ensureHumanIsNotProjectMember(this.api, this.projectId, testManagerLoginname);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
ensureHumanIsNotProjectMember(this.api, this.projectId, testManagerLoginname);
|
||||||
|
ensureHumanIsProjectMember(
|
||||||
|
this.api,
|
||||||
|
this.projectId,
|
||||||
|
testManagerLoginname,
|
||||||
|
roles.map((role) => role.internal),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
visitOwnedProject,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('roles', () => {
|
||||||
const testRoleName = 'e2eroleundertestname';
|
const testRoleName = 'e2eroleundertestname';
|
||||||
const testRoleDisplay = 'e2eroleundertestdisplay';
|
|
||||||
const testRoleGroup = 'e2eroleundertestgroup';
|
|
||||||
const testGrantName = 'e2egrantundertest';
|
|
||||||
|
|
||||||
var projectId: number;
|
beforeEach(function () {
|
||||||
|
ensureProjectResourceDoesntExist(this.api, this.projectId, Roles, testRoleName);
|
||||||
beforeEach(() => {
|
|
||||||
apiAuth().then((apiCalls) => {
|
|
||||||
ensureProjectExists(apiCalls, testProjectName).then((projId) => {
|
|
||||||
projectId = projId;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('add role', () => {
|
beforeEach(visitOwnedProject);
|
||||||
beforeEach(() => {
|
|
||||||
apiAuth().then((api) => {
|
|
||||||
ensureProjectResourceDoesntExist(api, projectId, Roles, testRoleName);
|
|
||||||
cy.visit(`/projects/${projectId}?id=roles`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add a role', () => {
|
it('should add a role', () => {
|
||||||
|
cy.get('[data-e2e="sidenav-element-roles"]').click();
|
||||||
cy.get('[data-e2e="add-new-role"]').click();
|
cy.get('[data-e2e="add-new-role"]').click();
|
||||||
cy.get('[formcontrolname="key"]').type(testRoleName);
|
cy.get('[formcontrolname="key"]').type(testRoleName);
|
||||||
cy.get('[formcontrolname="displayName"]').type(testRoleDisplay);
|
cy.get('[formcontrolname="displayName"]').type('e2eroleundertestdisplay');
|
||||||
cy.get('[formcontrolname="group"]').type(testRoleGroup);
|
cy.get('[formcontrolname="group"]').type('e2eroleundertestgroup');
|
||||||
cy.get('[data-e2e="save-button"]').click();
|
cy.get('[data-e2e="save-button"]').click();
|
||||||
cy.get('.data-e2e-success');
|
cy.get('.data-e2e-success');
|
||||||
cy.wait(200);
|
cy.contains('tr', testRoleName);
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
|
});
|
||||||
|
it('should remove a role');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('granted projects', () => {
|
||||||
|
beforeEach(function () {
|
||||||
|
ensureOrgExists(this.api, 'e2eforeignorg')
|
||||||
|
.as('foreignOrgId')
|
||||||
|
.then((foreignOrgId) => {
|
||||||
|
ensureProjectExists(this.api, 'e2eprojectgrants', foreignOrgId)
|
||||||
|
.as('projectId')
|
||||||
|
.then((projectId) => {
|
||||||
|
ensureProjectGrantExists(this.api, foreignOrgId, projectId).as('grantId');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
/*
|
|
||||||
|
|
||||||
describe('permissions', () => {
|
const visitGrantedProject: Mocha.HookFunction = function () {
|
||||||
|
cy.visit(`/granted-projects/${this.projectId}/grant/${this.grantId}`);
|
||||||
|
};
|
||||||
|
|
||||||
before(()=> {
|
describe('authorizations', () => {
|
||||||
// cy.consolelogin(Cypress.env('username'), Cypress.env('password'), Cypress.config('baseUrl')/ui/console)
|
const roles = [
|
||||||
})
|
{ internal: 'PROJECT_GRANT_OWNER', display: 'Project Grant Owner' },
|
||||||
|
{ internal: 'PROJECT_GRANT_OWNER_VIEWER', display: 'Project Grant Owner Viewer' },
|
||||||
|
];
|
||||||
|
|
||||||
it('should show projects ', () => {
|
testAuthorizations(
|
||||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/projects')
|
roles.map((role) => role.display),
|
||||||
cy.url().should('contain', '/projects')
|
function () {
|
||||||
})
|
ensureHumanIsNotProjectMember(this.api, this.projectId, testManagerLoginname, this.grantId);
|
||||||
|
},
|
||||||
it('should add a role', () => {
|
function () {
|
||||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/org').then(() => {
|
ensureHumanIsNotProjectMember(this.api, this.projectId, testManagerLoginname, this.grantId);
|
||||||
cy.url().should('contain', '/org');
|
ensureHumanIsProjectMember(
|
||||||
})
|
this.api,
|
||||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/projects').then(() => {
|
this.projectId,
|
||||||
cy.url().should('contain', '/projects');
|
testManagerLoginname,
|
||||||
cy.get('.card').should('contain.text', "newProjectToTest")
|
roles.map((role) => role.internal),
|
||||||
})
|
this.grantId,
|
||||||
cy.get('.card').filter(':contains("newProjectToTest")').click()
|
);
|
||||||
cy.get('.app-container').filter(':contains("newAppToTest")').should('be.visible').click()
|
},
|
||||||
let projectID
|
visitGrantedProject,
|
||||||
cy.url().then(url => {
|
);
|
||||||
cy.log(url.split('/')[4])
|
});
|
||||||
projectID = url.split('/')[4]
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.then(() => cy.visit(Cypress.config('baseUrl')/ui/console + '/projects/' + projectID +'/roles/create'))
|
describe('validations', () => {
|
||||||
cy.get('[formcontrolname^=key]').type("newdemorole")
|
describe('owned projects', () => {
|
||||||
cy.get('[formcontrolname^=displayName]').type("newdemodisplayname")
|
describe('no ownership', () => {
|
||||||
cy.get('[formcontrolname^=group]').type("newdemogroupname")
|
it('a user without project global ownership can ...');
|
||||||
cy.get('button').filter(':contains("Save")').should('be.visible').click()
|
it('a user without project global ownership can not ...');
|
||||||
//let the Role get processed
|
});
|
||||||
cy.wait(5000)
|
describe('project owner viewer global', () => {
|
||||||
})
|
it('a project owner viewer global additionally can ...');
|
||||||
|
it('a project owner viewer global still can not ...');
|
||||||
it('should add a grant', () => {
|
});
|
||||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/org').then(() => {
|
describe('project owner global', () => {
|
||||||
cy.url().should('contain', '/org');
|
it('a project owner global additionally can ...');
|
||||||
})
|
it('a project owner global still can not ...');
|
||||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/projects').then(() => {
|
});
|
||||||
cy.url().should('contain', '/projects');
|
|
||||||
cy.get('.card').should('contain.text', "newProjectToTest")
|
|
||||||
})
|
|
||||||
cy.get('.card').filter(':contains("newProjectToTest")').click()
|
|
||||||
cy.get('.app-container').filter(':contains("newAppToTest")').should('be.visible').click()
|
|
||||||
let projectID
|
|
||||||
cy.url().then(url => {
|
|
||||||
cy.log(url.split('/')[4])
|
|
||||||
projectID = url.split('/')[4]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.then(() => cy.visit(Cypress.config('baseUrl')/ui/console + '/grant-create/project/' + projectID ))
|
describe('granted projects', () => {
|
||||||
cy.get('input').type("demo")
|
describe('no ownership', () => {
|
||||||
cy.get('[role^=listbox]').filter(`:contains("${Cypress.env("fullUserName")}")`).should('be.visible').click()
|
it('a user without project grant ownership can ...');
|
||||||
cy.wait(5000)
|
it('a user without project grant ownership can not ...');
|
||||||
//cy.get('.button').contains('Continue').click()
|
});
|
||||||
cy.get('button').filter(':contains("Continue")').click()
|
describe('project grant owner viewer', () => {
|
||||||
cy.wait(5000)
|
it('a project grant owner viewer additionally can ...');
|
||||||
cy.get('tr').filter(':contains("demo")').find('label').click()
|
it('a project grant owner viewer still can not ...');
|
||||||
cy.get('button').filter(':contains("Save")').should('be.visible').click()
|
});
|
||||||
//let the grant get processed
|
describe('project grant owner', () => {
|
||||||
cy.wait(5000)
|
it('a project grant owner additionally can ...');
|
||||||
})
|
it('a project grant owner still can not ...');
|
||||||
})
|
});
|
||||||
|
});
|
||||||
*/
|
describe('organization', () => {
|
||||||
|
describe('org owner', () => {
|
||||||
|
it('a project owner global can ...');
|
||||||
|
it('a project owner global can not ...');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -2,15 +2,16 @@ import { apiAuth } from '../../support/api/apiauth';
|
|||||||
import { ensureProjectDoesntExist, ensureProjectExists } from '../../support/api/projects';
|
import { ensureProjectDoesntExist, ensureProjectExists } from '../../support/api/projects';
|
||||||
|
|
||||||
describe('projects', () => {
|
describe('projects', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apiAuth().as('api');
|
||||||
|
});
|
||||||
|
|
||||||
const testProjectNameCreate = 'e2eprojectcreate';
|
const testProjectNameCreate = 'e2eprojectcreate';
|
||||||
const testProjectNameDeleteList = 'e2eprojectdeletelist';
|
const testProjectNameDelete = 'e2eprojectdelete';
|
||||||
const testProjectNameDeleteGrid = 'e2eprojectdeletegrid';
|
|
||||||
|
|
||||||
describe('add project', () => {
|
describe('add project', () => {
|
||||||
beforeEach(`ensure it doesn't exist already`, () => {
|
beforeEach(`ensure it doesn't exist already`, function () {
|
||||||
apiAuth().then((api) => {
|
ensureProjectDoesntExist(this.api, testProjectNameCreate);
|
||||||
ensureProjectDoesntExist(api, testProjectNameCreate);
|
|
||||||
});
|
|
||||||
cy.visit(`/projects`);
|
cy.visit(`/projects`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -19,52 +20,43 @@ describe('projects', () => {
|
|||||||
cy.get('input').type(testProjectNameCreate);
|
cy.get('input').type(testProjectNameCreate);
|
||||||
cy.get('[data-e2e="continue-button"]').click();
|
cy.get('[data-e2e="continue-button"]').click();
|
||||||
cy.get('.data-e2e-success');
|
cy.get('.data-e2e-success');
|
||||||
cy.wait(200);
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should configure a project to assert roles on authentication');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('edit project', () => {
|
||||||
|
beforeEach('ensure it exists', function () {
|
||||||
|
ensureProjectExists(this.api, testProjectNameDelete);
|
||||||
|
cy.visit(`/projects`);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('remove project', () => {
|
describe('remove project', () => {
|
||||||
describe('list view', () => {
|
it('removes the project from list view', () => {
|
||||||
beforeEach('ensure it exists', () => {
|
const rowSelector = `tr:contains(${testProjectNameDelete})`;
|
||||||
apiAuth().then((api) => {
|
|
||||||
ensureProjectExists(api, testProjectNameDeleteList);
|
|
||||||
});
|
|
||||||
cy.visit(`/projects`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes the project', () => {
|
|
||||||
cy.get('[data-e2e="toggle-grid"]').click();
|
cy.get('[data-e2e="toggle-grid"]').click();
|
||||||
cy.get('[data-e2e="timestamp"]');
|
cy.get('[data-e2e="timestamp"]');
|
||||||
cy.contains('tr', testProjectNameDeleteList, { timeout: 1000 })
|
cy.get(rowSelector).find('[data-e2e="delete-project-button"]').click({ force: true });
|
||||||
.find('[data-e2e="delete-project-button"]')
|
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testProjectNameDelete);
|
||||||
.click({ force: true });
|
|
||||||
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testProjectNameDeleteList);
|
|
||||||
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
||||||
cy.get('.data-e2e-success');
|
cy.get('.data-e2e-success');
|
||||||
cy.wait(200);
|
cy.shouldNotExist({ selector: rowSelector, timeout: 2000 });
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes the project from grid view', () => {
|
||||||
|
const cardSelector = `[data-e2e="grid-card"]:contains(${testProjectNameDelete})`;
|
||||||
|
cy.get(cardSelector).find('[data-e2e="delete-project-button"]').click({ force: true });
|
||||||
|
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testProjectNameDelete);
|
||||||
|
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
||||||
|
cy.get('.data-e2e-success');
|
||||||
|
cy.shouldNotExist({ selector: cardSelector, timeout: 2000 });
|
||||||
|
cy.shouldNotExist({ selector: '.data-e2e-failure' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('grid view', () => {
|
it('should add a project manager');
|
||||||
beforeEach('ensure it exists', () => {
|
it('should remove a project manager');
|
||||||
apiAuth().then((api) => {
|
|
||||||
ensureProjectExists(api, testProjectNameDeleteGrid);
|
|
||||||
});
|
|
||||||
cy.visit(`/projects`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes the project', () => {
|
|
||||||
cy.contains('[data-e2e="grid-card"]', testProjectNameDeleteGrid)
|
|
||||||
.find('[data-e2e="delete-project-button"]')
|
|
||||||
.click({ force: true });
|
|
||||||
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(testProjectNameDeleteGrid);
|
|
||||||
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
|
||||||
cy.get('.data-e2e-success');
|
|
||||||
cy.wait(200);
|
|
||||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -8,7 +8,7 @@ describe('oidc settings', () => {
|
|||||||
const refreshTokenExpirationPrecondition = 7;
|
const refreshTokenExpirationPrecondition = 7;
|
||||||
const refreshTokenIdleExpirationPrecondition = 2;
|
const refreshTokenIdleExpirationPrecondition = 2;
|
||||||
|
|
||||||
before(`ensure they are set`, () => {
|
beforeEach(`ensure they are set`, () => {
|
||||||
apiAuth().then((apiCallProperties) => {
|
apiAuth().then((apiCallProperties) => {
|
||||||
ensureOIDCSettingsSet(
|
ensureOIDCSettingsSet(
|
||||||
apiCallProperties,
|
apiCallProperties,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { apiAuth, apiCallProperties } from '../../support/api/apiauth';
|
import { apiAuth, API } from '../../support/api/apiauth';
|
||||||
import { Policy, resetPolicy } from '../../support/api/policies';
|
import { Policy, resetPolicy } from '../../support/api/policies';
|
||||||
import { login, User } from '../../support/login/users';
|
import { login, User } from '../../support/login/users';
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ describe('private labeling', () => {
|
|||||||
|
|
||||||
[User.OrgOwner].forEach((user) => {
|
[User.OrgOwner].forEach((user) => {
|
||||||
describe(`as user "${user}"`, () => {
|
describe(`as user "${user}"`, () => {
|
||||||
let api: apiCallProperties;
|
let api: API;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
login(user);
|
login(user);
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
import { login, User } from 'support/login/users';
|
import { login, User } from 'support/login/users';
|
||||||
|
import { API } from './types';
|
||||||
|
|
||||||
export interface apiCallProperties {
|
const authHeaderKey = 'Authorization',
|
||||||
authHeader: string;
|
orgIdHeaderKey = 'x-zitadel-orgid';
|
||||||
mgntBaseURL: string;
|
|
||||||
adminBaseURL: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apiAuth(): Cypress.Chainable<apiCallProperties> {
|
export function apiAuth(): Cypress.Chainable<API> {
|
||||||
return login(User.IAMAdminUser, 'Password1!', false, true).then((token) => {
|
return login(User.IAMAdminUser, 'Password1!', false, true).then((token) => {
|
||||||
return <apiCallProperties>{
|
return <API>{
|
||||||
authHeader: `Bearer ${token}`,
|
token: token,
|
||||||
mgntBaseURL: `${Cypress.env('BACKEND_URL')}/management/v1/`,
|
mgmtBaseURL: `${Cypress.env('BACKEND_URL')}/management/v1`,
|
||||||
adminBaseURL: `${Cypress.env('BACKEND_URL')}/admin/v1/`,
|
adminBaseURL: `${Cypress.env('BACKEND_URL')}/admin/v1`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function requestHeaders(api: API, orgId?: number): object {
|
||||||
|
const headers = { [authHeaderKey]: `Bearer ${api.token}` };
|
||||||
|
if (orgId) {
|
||||||
|
headers[orgIdHeaderKey] = orgId;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
@ -1,196 +1,123 @@
|
|||||||
import { apiCallProperties } from './apiauth';
|
import { requestHeaders } from './apiauth';
|
||||||
|
import { findFromList as mapFromList, searchSomething } from './search';
|
||||||
|
import { API, Entity, SearchResult } from './types';
|
||||||
|
|
||||||
export function ensureSomethingExists(
|
export function ensureItemExists(
|
||||||
api: apiCallProperties,
|
api: API,
|
||||||
searchPath: string,
|
searchPath: string,
|
||||||
find: (entity: any) => boolean,
|
findInList: (entity: Entity) => boolean,
|
||||||
createPath: string,
|
createPath: string,
|
||||||
body: any,
|
body: Entity,
|
||||||
|
orgId?: number,
|
||||||
|
newItemIdField: string = 'id',
|
||||||
|
searchItemIdField?: string,
|
||||||
): Cypress.Chainable<number> {
|
): Cypress.Chainable<number> {
|
||||||
return searchSomething(api, searchPath, find)
|
return ensureSomething(
|
||||||
.then((sRes) => {
|
api,
|
||||||
if (sRes.entity) {
|
() => searchSomething(api, searchPath, 'POST', mapFromList(findInList, searchItemIdField), orgId),
|
||||||
return cy.wrap({
|
() => createPath,
|
||||||
id: sRes.entity.id,
|
'POST',
|
||||||
initialSequence: 0,
|
body,
|
||||||
});
|
(entity) => !!entity,
|
||||||
}
|
(body) => body[newItemIdField],
|
||||||
return cy
|
orgId,
|
||||||
.request({
|
);
|
||||||
method: 'POST',
|
|
||||||
url: `${api.mgntBaseURL}${createPath}`,
|
|
||||||
headers: {
|
|
||||||
Authorization: api.authHeader,
|
|
||||||
},
|
|
||||||
body: body,
|
|
||||||
failOnStatusCode: false,
|
|
||||||
followRedirect: false,
|
|
||||||
})
|
|
||||||
.then((cRes) => {
|
|
||||||
expect(cRes.status).to.equal(200);
|
|
||||||
return {
|
|
||||||
id: cRes.body.id,
|
|
||||||
initialSequence: sRes.sequence,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
awaitDesired(90, (entity) => !!entity, data.initialSequence, api, searchPath, find);
|
|
||||||
return cy.wrap<number>(data.id);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureSomethingIsSet(
|
export function ensureItemDoesntExist(
|
||||||
api: apiCallProperties,
|
api: API,
|
||||||
path: string,
|
|
||||||
find: (entity: any) => SearchResult,
|
|
||||||
createPath: string,
|
|
||||||
body: any,
|
|
||||||
): Cypress.Chainable<number> {
|
|
||||||
return getSomething(api, path, find)
|
|
||||||
.then((sRes) => {
|
|
||||||
if (sRes.entity) {
|
|
||||||
return cy.wrap({
|
|
||||||
id: sRes.entity.id,
|
|
||||||
initialSequence: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return cy
|
|
||||||
.request({
|
|
||||||
method: 'PUT',
|
|
||||||
url: createPath,
|
|
||||||
headers: {
|
|
||||||
Authorization: api.authHeader,
|
|
||||||
},
|
|
||||||
body: body,
|
|
||||||
failOnStatusCode: false,
|
|
||||||
followRedirect: false,
|
|
||||||
})
|
|
||||||
.then((cRes) => {
|
|
||||||
expect(cRes.status).to.equal(200);
|
|
||||||
return {
|
|
||||||
id: cRes.body.id,
|
|
||||||
initialSequence: sRes.sequence,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
awaitDesiredById(90, (entity) => !!entity, data.initialSequence, api, path, find);
|
|
||||||
return cy.wrap<number>(data.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ensureSomethingDoesntExist(
|
|
||||||
api: apiCallProperties,
|
|
||||||
searchPath: string,
|
searchPath: string,
|
||||||
find: (entity: any) => boolean,
|
findInList: (entity: Entity) => boolean,
|
||||||
deletePath: (entity: any) => string,
|
deletePath: (entity: Entity) => string,
|
||||||
|
orgId?: number,
|
||||||
): Cypress.Chainable<null> {
|
): Cypress.Chainable<null> {
|
||||||
return searchSomething(api, searchPath, find)
|
return ensureSomething(
|
||||||
.then((sRes) => {
|
api,
|
||||||
if (!sRes.entity) {
|
() => searchSomething(api, searchPath, 'POST', mapFromList(findInList), orgId),
|
||||||
return cy.wrap(0);
|
deletePath,
|
||||||
}
|
'DELETE',
|
||||||
return cy
|
null,
|
||||||
.request({
|
(entity) => !entity,
|
||||||
method: 'DELETE',
|
).then(() => null);
|
||||||
url: `${api.mgntBaseURL}${deletePath(sRes.entity)}`,
|
|
||||||
headers: {
|
|
||||||
Authorization: api.authHeader,
|
|
||||||
},
|
|
||||||
failOnStatusCode: false,
|
|
||||||
})
|
|
||||||
.then((dRes) => {
|
|
||||||
expect(dRes.status).to.equal(200);
|
|
||||||
return sRes.sequence;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((initialSequence) => {
|
|
||||||
awaitDesired(90, (entity) => !entity, initialSequence, api, searchPath, find);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchResult = {
|
export function ensureSetting(
|
||||||
entity: any;
|
api: API,
|
||||||
sequence: number;
|
path: string,
|
||||||
};
|
mapResult: (entity: any) => SearchResult,
|
||||||
|
createPath: string,
|
||||||
function searchSomething(
|
body: any,
|
||||||
api: apiCallProperties,
|
orgId?: number,
|
||||||
searchPath: string,
|
): Cypress.Chainable<number> {
|
||||||
find: (entity: any) => boolean,
|
return ensureSomething(
|
||||||
): Cypress.Chainable<SearchResult> {
|
api,
|
||||||
return cy
|
() => searchSomething(api, path, 'GET', mapResult, orgId),
|
||||||
.request({
|
() => createPath,
|
||||||
method: 'POST',
|
'PUT',
|
||||||
url: `${api.mgntBaseURL}${searchPath}`,
|
body,
|
||||||
headers: {
|
(entity) => !!entity,
|
||||||
Authorization: api.authHeader,
|
(body) => body?.settings?.id,
|
||||||
},
|
);
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
return {
|
|
||||||
entity: res.body.result?.find(find) || null,
|
|
||||||
sequence: res.body.details.processedSequence,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSomething(
|
|
||||||
api: apiCallProperties,
|
|
||||||
searchPath: string,
|
|
||||||
find: (entity: any) => SearchResult,
|
|
||||||
): Cypress.Chainable<SearchResult> {
|
|
||||||
return cy
|
|
||||||
.request({
|
|
||||||
method: 'GET',
|
|
||||||
url: searchPath,
|
|
||||||
headers: {
|
|
||||||
Authorization: api.authHeader,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
return find(res.body);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function awaitDesired(
|
function awaitDesired(
|
||||||
trials: number,
|
trials: number,
|
||||||
expectEntity: (entity: any) => boolean,
|
expectEntity: (entity: Entity) => boolean,
|
||||||
initialSequence: number,
|
search: () => Cypress.Chainable<SearchResult>,
|
||||||
api: apiCallProperties,
|
initialSequence?: number,
|
||||||
searchPath: string,
|
|
||||||
find: (entity: any) => boolean,
|
|
||||||
) {
|
) {
|
||||||
searchSomething(api, searchPath, find).then((resp) => {
|
search().then((resp) => {
|
||||||
const foundExpectedEntity = expectEntity(resp.entity);
|
const foundExpectedEntity = expectEntity(resp.entity);
|
||||||
const foundExpectedSequence = resp.sequence > initialSequence;
|
const foundExpectedSequence = !initialSequence || resp.sequence >= initialSequence;
|
||||||
|
|
||||||
if (!foundExpectedEntity || !foundExpectedSequence) {
|
if (!foundExpectedEntity || !foundExpectedSequence) {
|
||||||
expect(trials, `trying ${trials} more times`).to.be.greaterThan(0);
|
expect(trials, `trying ${trials} more times`).to.be.greaterThan(0);
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
awaitDesired(trials - 1, expectEntity, initialSequence, api, searchPath, find);
|
awaitDesired(trials - 1, expectEntity, search, initialSequence);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function awaitDesiredById(
|
interface EnsuredResult {
|
||||||
trials: number,
|
id: number;
|
||||||
expectEntity: (entity: any) => boolean,
|
sequence: number;
|
||||||
initialSequence: number,
|
|
||||||
api: apiCallProperties,
|
|
||||||
path: string,
|
|
||||||
find: (entity: any) => SearchResult,
|
|
||||||
) {
|
|
||||||
getSomething(api, path, find).then((resp) => {
|
|
||||||
const foundExpectedEntity = expectEntity(resp.entity);
|
|
||||||
const foundExpectedSequence = resp.sequence > initialSequence;
|
|
||||||
|
|
||||||
if (!foundExpectedEntity || !foundExpectedSequence) {
|
|
||||||
expect(trials, `trying ${trials} more times`).to.be.greaterThan(0);
|
|
||||||
cy.wait(1000);
|
|
||||||
awaitDesiredById(trials - 1, expectEntity, initialSequence, api, path, find);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensureSomething(
|
||||||
|
api: API,
|
||||||
|
search: () => Cypress.Chainable<SearchResult>,
|
||||||
|
apiPath: (entity: Entity) => string,
|
||||||
|
ensureMethod: string,
|
||||||
|
body: Entity,
|
||||||
|
expectEntity: (entity: Entity) => boolean,
|
||||||
|
mapId?: (body: any) => number,
|
||||||
|
orgId?: number,
|
||||||
|
): Cypress.Chainable<number> {
|
||||||
|
return search()
|
||||||
|
.then<EnsuredResult>((sRes) => {
|
||||||
|
if (expectEntity(sRes.entity)) {
|
||||||
|
return cy.wrap({ id: sRes.id, sequence: sRes.sequence });
|
||||||
|
}
|
||||||
|
|
||||||
|
return cy
|
||||||
|
.request({
|
||||||
|
method: ensureMethod,
|
||||||
|
url: apiPath(sRes.entity),
|
||||||
|
headers: requestHeaders(api, orgId),
|
||||||
|
body: body,
|
||||||
|
failOnStatusCode: false,
|
||||||
|
followRedirect: false,
|
||||||
|
})
|
||||||
|
.then((cRes) => {
|
||||||
|
expect(cRes.status).to.equal(200);
|
||||||
|
return {
|
||||||
|
id: mapId ? mapId(cRes.body) : undefined,
|
||||||
|
sequence: sRes.sequence,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
awaitDesired(90, expectEntity, search, data.sequence);
|
||||||
|
return cy.wrap<number>(data.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
22
e2e/cypress/support/api/grants.ts
Normal file
22
e2e/cypress/support/api/grants.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ensureItemExists } from './ensure';
|
||||||
|
import { getOrgUnderTest } from './orgs';
|
||||||
|
import { API } from './types';
|
||||||
|
|
||||||
|
export function ensureProjectGrantExists(
|
||||||
|
api: API,
|
||||||
|
foreignOrgId: number,
|
||||||
|
foreignProjectId: number,
|
||||||
|
): Cypress.Chainable<number> {
|
||||||
|
return getOrgUnderTest(api).then((orgUnderTest) => {
|
||||||
|
return ensureItemExists(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/projectgrants/_search`,
|
||||||
|
(grant: any) => grant.grantedOrgId == orgUnderTest && grant.projectId == foreignProjectId,
|
||||||
|
`${api.mgmtBaseURL}/projects/${foreignProjectId}/grants`,
|
||||||
|
{ granted_org_id: orgUnderTest },
|
||||||
|
foreignOrgId,
|
||||||
|
'grantId',
|
||||||
|
'grantId',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
76
e2e/cypress/support/api/members.ts
Normal file
76
e2e/cypress/support/api/members.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { ensureItemDoesntExist, ensureItemExists } from './ensure';
|
||||||
|
import { findFromList, searchSomething } from './search';
|
||||||
|
import { API } from './types';
|
||||||
|
|
||||||
|
export function ensureHumanIsNotOrgMember(api: API, username: string): Cypress.Chainable<number> {
|
||||||
|
return ensureItemDoesntExist(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/orgs/me/members/_search`,
|
||||||
|
(member: any) => (<string>member.preferredLoginName).startsWith(username),
|
||||||
|
(member) => `${api.mgmtBaseURL}/orgs/me/members/${member.userId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureHumanIsOrgMember(api: API, username: string, roles: string[]): Cypress.Chainable<number> {
|
||||||
|
return searchSomething(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/users/_search`,
|
||||||
|
'POST',
|
||||||
|
findFromList((user) => {
|
||||||
|
return user.userName == username;
|
||||||
|
}),
|
||||||
|
).then((user) => {
|
||||||
|
return ensureItemExists(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/orgs/me/members/_search`,
|
||||||
|
(member: any) => member.userId == user.entity.id,
|
||||||
|
`${api.mgmtBaseURL}/orgs/me/members`,
|
||||||
|
{
|
||||||
|
userId: user.entity.id,
|
||||||
|
roles: roles,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureHumanIsNotProjectMember(
|
||||||
|
api: API,
|
||||||
|
projectId: string,
|
||||||
|
username: string,
|
||||||
|
grantId?: number,
|
||||||
|
): Cypress.Chainable<number> {
|
||||||
|
return ensureItemDoesntExist(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/projects/${projectId}/${grantId ? `grants/${grantId}/` : ''}members/_search`,
|
||||||
|
(member: any) => (<string>member.preferredLoginName).startsWith(username),
|
||||||
|
(member) => `${api.mgmtBaseURL}/projects/${projectId}${grantId ? `grants/${grantId}/` : ''}/members/${member.userId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureHumanIsProjectMember(
|
||||||
|
api: API,
|
||||||
|
projectId: string,
|
||||||
|
username: string,
|
||||||
|
roles: string[],
|
||||||
|
grantId?: number,
|
||||||
|
): Cypress.Chainable<number> {
|
||||||
|
return searchSomething(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/users/_search`,
|
||||||
|
'POST',
|
||||||
|
findFromList((user) => {
|
||||||
|
return user.userName == username;
|
||||||
|
}),
|
||||||
|
).then((user) => {
|
||||||
|
return ensureItemExists(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/projects/${projectId}/${grantId ? `grants/${grantId}/` : ''}members/_search`,
|
||||||
|
(member: any) => member.userId == user.entity.id,
|
||||||
|
`${api.mgmtBaseURL}/projects/${projectId}/${grantId ? `grants/${grantId}/` : ''}members`,
|
||||||
|
{
|
||||||
|
userId: user.entity.id,
|
||||||
|
roles: roles,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -1,32 +1,35 @@
|
|||||||
import { apiCallProperties } from './apiauth';
|
import { ensureSetting } from './ensure';
|
||||||
import { ensureSomethingIsSet } from './ensure';
|
import { API } from './types';
|
||||||
|
|
||||||
export function ensureOIDCSettingsSet(
|
export function ensureOIDCSettingsSet(
|
||||||
api: apiCallProperties,
|
api: API,
|
||||||
accessTokenLifetime,
|
accessTokenLifetime: number,
|
||||||
idTokenLifetime,
|
idTokenLifetime: number,
|
||||||
refreshTokenExpiration,
|
refreshTokenExpiration: number,
|
||||||
refreshTokenIdleExpiration: number,
|
refreshTokenIdleExpiration: number,
|
||||||
): Cypress.Chainable<number> {
|
): Cypress.Chainable<number> {
|
||||||
return ensureSomethingIsSet(
|
return ensureSetting(
|
||||||
api,
|
api,
|
||||||
`${api.adminBaseURL}settings/oidc`,
|
`${api.adminBaseURL}/settings/oidc`,
|
||||||
(settings: any) => {
|
(body: any) => {
|
||||||
let entity = null;
|
const result = {
|
||||||
if (
|
sequence: body.settings?.details?.sequence,
|
||||||
settings.settings?.accessTokenLifetime === hoursToDuration(accessTokenLifetime) &&
|
id: body.settings.id,
|
||||||
settings.settings?.idTokenLifetime === hoursToDuration(idTokenLifetime) &&
|
entity: null,
|
||||||
settings.settings?.refreshTokenExpiration === daysToDuration(refreshTokenExpiration) &&
|
|
||||||
settings.settings?.refreshTokenIdleExpiration === daysToDuration(refreshTokenIdleExpiration)
|
|
||||||
) {
|
|
||||||
entity = settings.settings;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
entity: entity,
|
|
||||||
sequence: settings.settings?.details?.sequence,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
body.settings &&
|
||||||
|
body.settings.accessTokenLifetime === hoursToDuration(accessTokenLifetime) &&
|
||||||
|
body.settings.idTokenLifetime === hoursToDuration(idTokenLifetime) &&
|
||||||
|
body.settings.refreshTokenExpiration === daysToDuration(refreshTokenExpiration) &&
|
||||||
|
body.settings.refreshTokenIdleExpiration === daysToDuration(refreshTokenIdleExpiration)
|
||||||
|
) {
|
||||||
|
return { ...result, entity: body.settings };
|
||||||
|
}
|
||||||
|
return result;
|
||||||
},
|
},
|
||||||
`${api.adminBaseURL}settings/oidc`,
|
`${api.adminBaseURL}/settings/oidc`,
|
||||||
{
|
{
|
||||||
accessTokenLifetime: hoursToDuration(accessTokenLifetime),
|
accessTokenLifetime: hoursToDuration(accessTokenLifetime),
|
||||||
idTokenLifetime: hoursToDuration(idTokenLifetime),
|
idTokenLifetime: hoursToDuration(idTokenLifetime),
|
||||||
|
30
e2e/cypress/support/api/orgs.ts
Normal file
30
e2e/cypress/support/api/orgs.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ensureSomething } from './ensure';
|
||||||
|
import { searchSomething } from './search';
|
||||||
|
import { API } from './types';
|
||||||
|
import { host } from '../login/users';
|
||||||
|
|
||||||
|
export function ensureOrgExists(api: API, name: string): Cypress.Chainable<number> {
|
||||||
|
return ensureSomething(
|
||||||
|
api,
|
||||||
|
() =>
|
||||||
|
searchSomething(
|
||||||
|
api,
|
||||||
|
encodeURI(`${api.mgmtBaseURL}/global/orgs/_by_domain?domain=${name}.${host(Cypress.config('baseUrl'))}`),
|
||||||
|
'GET',
|
||||||
|
(res) => {
|
||||||
|
return { entity: res.org, id: res.org?.id, sequence: res.org?.details?.sequence };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
() => `${api.mgmtBaseURL}/orgs`,
|
||||||
|
'POST',
|
||||||
|
{ name: name },
|
||||||
|
(org: any) => org?.name === name,
|
||||||
|
(res) => res.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrgUnderTest(api: API): Cypress.Chainable<number> {
|
||||||
|
return searchSomething(api, `${api.mgmtBaseURL}/orgs/me`, 'GET', (res) => {
|
||||||
|
return { entity: res.org, id: res.org.id, sequence: res.org.details.sequence };
|
||||||
|
}).then((res) => res.entity.id);
|
||||||
|
}
|
@ -1,16 +1,15 @@
|
|||||||
import { apiCallProperties } from './apiauth';
|
import { requestHeaders } from './apiauth';
|
||||||
|
import { API } from './types';
|
||||||
|
|
||||||
export enum Policy {
|
export enum Policy {
|
||||||
Label = 'label',
|
Label = 'label',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetPolicy(api: apiCallProperties, policy: Policy) {
|
export function resetPolicy(api: API, policy: Policy) {
|
||||||
cy.request({
|
cy.request({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `${api.mgntBaseURL}/policies/${policy}`,
|
url: `${api.mgmtBaseURL}/policies/${policy}`,
|
||||||
headers: {
|
headers: requestHeaders(api),
|
||||||
Authorization: api.authHeader,
|
|
||||||
},
|
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
expect(res.status).to.equal(200);
|
expect(res.status).to.equal(200);
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
import { apiCallProperties } from './apiauth';
|
import { ensureItemDoesntExist, ensureItemExists } from './ensure';
|
||||||
import { ensureSomethingDoesntExist, ensureSomethingExists } from './ensure';
|
import { API } from './types';
|
||||||
|
|
||||||
export function ensureProjectExists(api: apiCallProperties, projectName: string): Cypress.Chainable<number> {
|
export function ensureProjectExists(api: API, projectName: string, orgId?: number): Cypress.Chainable<number> {
|
||||||
return ensureSomethingExists(api, `projects/_search`, (project: any) => project.name === projectName, 'projects', {
|
return ensureItemExists(
|
||||||
name: projectName,
|
api,
|
||||||
});
|
`${api.mgmtBaseURL}/projects/_search`,
|
||||||
|
(project: any) => project.name === projectName,
|
||||||
|
`${api.mgmtBaseURL}/projects`,
|
||||||
|
{ name: projectName },
|
||||||
|
orgId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureProjectDoesntExist(api: apiCallProperties, projectName: string): Cypress.Chainable<null> {
|
export function ensureProjectDoesntExist(api: API, projectName: string, orgId?: number): Cypress.Chainable<null> {
|
||||||
return ensureSomethingDoesntExist(
|
return ensureItemDoesntExist(
|
||||||
api,
|
api,
|
||||||
`projects/_search`,
|
`${api.mgmtBaseURL}/projects/_search`,
|
||||||
(project: any) => project.name === projectName,
|
(project: any) => project.name === projectName,
|
||||||
(project) => `projects/${project.id}`,
|
(project) => `${api.mgmtBaseURL}/projects/${project.id}`,
|
||||||
|
orgId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,33 +31,28 @@ export const Roles = new ResourceType('roles', 'key', 'key');
|
|||||||
//export const Grants = new ResourceType('apps', 'name')
|
//export const Grants = new ResourceType('apps', 'name')
|
||||||
|
|
||||||
export function ensureProjectResourceDoesntExist(
|
export function ensureProjectResourceDoesntExist(
|
||||||
api: apiCallProperties,
|
api: API,
|
||||||
projectId: number,
|
projectId: number,
|
||||||
resourceType: ResourceType,
|
resourceType: ResourceType,
|
||||||
resourceName: string,
|
resourceName: string,
|
||||||
|
orgId?: number,
|
||||||
): Cypress.Chainable<null> {
|
): Cypress.Chainable<null> {
|
||||||
return ensureSomethingDoesntExist(
|
return ensureItemDoesntExist(
|
||||||
api,
|
api,
|
||||||
`projects/${projectId}/${resourceType.resourcePath}/_search`,
|
`${api.mgmtBaseURL}/projects/${projectId}/${resourceType.resourcePath}/_search`,
|
||||||
(resource: any) => {
|
(resource: any) => resource[resourceType.compareProperty] === resourceName,
|
||||||
return resource[resourceType.compareProperty] === resourceName;
|
(resource) =>
|
||||||
},
|
`${api.mgmtBaseURL}/projects/${projectId}/${resourceType.resourcePath}/${resource[resourceType.identifierProperty]}`,
|
||||||
(resource) => {
|
orgId,
|
||||||
return `projects/${projectId}/${resourceType.resourcePath}/${resource[resourceType.identifierProperty]}`;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureApplicationExists(
|
export function ensureApplicationExists(api: API, projectId: number, appName: string): Cypress.Chainable<number> {
|
||||||
api: apiCallProperties,
|
return ensureItemExists(
|
||||||
projectId: number,
|
|
||||||
appName: string,
|
|
||||||
): Cypress.Chainable<number> {
|
|
||||||
return ensureSomethingExists(
|
|
||||||
api,
|
api,
|
||||||
`projects/${projectId}/${Apps.resourcePath}/_search`,
|
`${api.mgmtBaseURL}/projects/${projectId}/${Apps.resourcePath}/_search`,
|
||||||
(resource: any) => resource.name === appName,
|
(resource: any) => resource.name === appName,
|
||||||
`projects/${projectId}/${Apps.resourcePath}/oidc`,
|
`${api.mgmtBaseURL}/projects/${projectId}/${Apps.resourcePath}/oidc`,
|
||||||
{
|
{
|
||||||
name: appName,
|
name: appName,
|
||||||
redirectUris: ['https://e2eredirecturl.org'],
|
redirectUris: ['https://e2eredirecturl.org'],
|
||||||
@ -59,11 +60,6 @@ export function ensureApplicationExists(
|
|||||||
grantTypes: ['OIDC_GRANT_TYPE_AUTHORIZATION_CODE'],
|
grantTypes: ['OIDC_GRANT_TYPE_AUTHORIZATION_CODE'],
|
||||||
authMethodType: 'OIDC_AUTH_METHOD_TYPE_NONE',
|
authMethodType: 'OIDC_AUTH_METHOD_TYPE_NONE',
|
||||||
postLogoutRedirectUris: ['https://e2elogoutredirecturl.org'],
|
postLogoutRedirectUris: ['https://e2elogoutredirecturl.org'],
|
||||||
/* "clientId": "129383004379407963@e2eprojectpermission",
|
|
||||||
"clockSkew": "0s",
|
|
||||||
"allowedOrigins": [
|
|
||||||
"https://testurl.org"
|
|
||||||
]*/
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
32
e2e/cypress/support/api/search.ts
Normal file
32
e2e/cypress/support/api/search.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { requestHeaders } from './apiauth';
|
||||||
|
import { API, Entity, SearchResult } from './types';
|
||||||
|
|
||||||
|
export function searchSomething(
|
||||||
|
api: API,
|
||||||
|
searchPath: string,
|
||||||
|
method: string,
|
||||||
|
mapResult: (body: any) => SearchResult,
|
||||||
|
orgId?: number,
|
||||||
|
): Cypress.Chainable<SearchResult> {
|
||||||
|
return cy
|
||||||
|
.request({
|
||||||
|
method: method,
|
||||||
|
url: searchPath,
|
||||||
|
headers: requestHeaders(api, orgId),
|
||||||
|
failOnStatusCode: method == 'POST',
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
return mapResult(res.body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findFromList(find: (entity: Entity) => boolean, idField: string = 'id'): (body: any) => SearchResult {
|
||||||
|
return (b) => {
|
||||||
|
const entity = b.result?.find(find);
|
||||||
|
return {
|
||||||
|
entity: entity,
|
||||||
|
sequence: parseInt(<string>b.details.processedSequence),
|
||||||
|
id: entity?.[idField],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
14
e2e/cypress/support/api/types.ts
Normal file
14
e2e/cypress/support/api/types.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export interface API {
|
||||||
|
token: string;
|
||||||
|
mgmtBaseURL: string;
|
||||||
|
adminBaseURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SearchResult = {
|
||||||
|
entity: Entity | null;
|
||||||
|
sequence: number;
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Entity is an object but not a function
|
||||||
|
export type Entity = { [k: string]: any } & ({ bind?: never } | { call?: never });
|
@ -1,8 +1,13 @@
|
|||||||
import { apiCallProperties } from './apiauth';
|
import { ensureItemDoesntExist, ensureItemExists } from './ensure';
|
||||||
import { ensureSomethingDoesntExist, ensureSomethingExists } from './ensure';
|
import { API } from './types';
|
||||||
|
|
||||||
export function ensureHumanUserExists(api: apiCallProperties, username: string): Cypress.Chainable<number> {
|
export function ensureHumanUserExists(api: API, username: string): Cypress.Chainable<number> {
|
||||||
return ensureSomethingExists(api, 'users/_search', (user: any) => user.userName === username, 'users/human', {
|
return ensureItemExists(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/users/_search`,
|
||||||
|
(user: any) => user.userName === username,
|
||||||
|
`${api.mgmtBaseURL}/users/human`,
|
||||||
|
{
|
||||||
user_name: username,
|
user_name: username,
|
||||||
profile: {
|
profile: {
|
||||||
first_name: 'e2efirstName',
|
first_name: 'e2efirstName',
|
||||||
@ -14,22 +19,33 @@ export function ensureHumanUserExists(api: apiCallProperties, username: string):
|
|||||||
phone: {
|
phone: {
|
||||||
phone: '+41 123456789',
|
phone: '+41 123456789',
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
undefined,
|
||||||
|
'userId',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureMachineUserExists(api: apiCallProperties, username: string): Cypress.Chainable<number> {
|
export function ensureMachineUserExists(api: API, username: string): Cypress.Chainable<number> {
|
||||||
return ensureSomethingExists(api, 'users/_search', (user: any) => user.userName === username, 'users/machine', {
|
return ensureItemExists(
|
||||||
|
api,
|
||||||
|
`${api.mgmtBaseURL}/users/_search`,
|
||||||
|
(user: any) => user.userName === username,
|
||||||
|
`${api.mgmtBaseURL}/users/machine`,
|
||||||
|
{
|
||||||
user_name: username,
|
user_name: username,
|
||||||
name: 'e2emachinename',
|
name: 'e2emachinename',
|
||||||
description: 'e2emachinedescription',
|
description: 'e2emachinedescription',
|
||||||
});
|
},
|
||||||
}
|
undefined,
|
||||||
|
'userId',
|
||||||
export function ensureUserDoesntExist(api: apiCallProperties, username: string): Cypress.Chainable<null> {
|
);
|
||||||
return ensureSomethingDoesntExist(
|
}
|
||||||
api,
|
|
||||||
'users/_search',
|
export function ensureUserDoesntExist(api: API, username: string): Cypress.Chainable<null> {
|
||||||
(user: any) => user.userName === username,
|
return ensureItemDoesntExist(
|
||||||
(user) => `users/${user.id}`,
|
api,
|
||||||
|
`${api.mgmtBaseURL}/users/_search`,
|
||||||
|
(user: any) => user.userName === username,
|
||||||
|
(user) => `${api.mgmtBaseURL}/users/${user.id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,80 @@
|
|||||||
/*
|
import 'cypress-wait-until';
|
||||||
|
//
|
||||||
|
//namespace Cypress {
|
||||||
|
// interface Chainable {
|
||||||
|
// /**
|
||||||
|
// * Custom command that authenticates a user.
|
||||||
|
// *
|
||||||
|
// * @example cy.consolelogin('hodor', 'hodor1234')
|
||||||
|
// */
|
||||||
|
// consolelogin(username: string, password: string): void
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//Cypress.Commands.add('consolelogin', { prevSubject: false }, (username: string, password: string) => {
|
||||||
|
//
|
||||||
|
// window.sessionStorage.removeItem("zitadel:access_token")
|
||||||
|
// cy.visit(Cypress.config('baseUrl')/ui/console).then(() => {
|
||||||
|
// // fill the fields and push button
|
||||||
|
// cy.get('#loginName').type(username, { log: false })
|
||||||
|
// cy.get('#submit-button').click()
|
||||||
|
// cy.get('#password').type(password, { log: false })
|
||||||
|
// cy.get('#submit-button').click()
|
||||||
|
// cy.location('pathname', {timeout: 5 * 1000}).should('eq', '/');
|
||||||
|
// })
|
||||||
|
//})
|
||||||
|
//
|
||||||
|
|
||||||
|
interface ShouldNotExistOptions {
|
||||||
|
selector?: string;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
interface Chainable {
|
interface Chainable {
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* Custom command that authenticates a user.
|
* Custom command that asserts on clipboard text.
|
||||||
*
|
*
|
||||||
* @example cy.consolelogin('hodor', 'hodor1234')
|
* @example cy.clipboardMatches('hodor', 'hodor1234')
|
||||||
*/
|
*/
|
||||||
/* consolelogin(username: string, password: string): void
|
clipboardMatches(pattern: RegExp | string): Cypress.Chainable<null>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom command that waits until the selector finds zero elements.
|
||||||
|
*/
|
||||||
|
shouldNotExist(options?: ShouldNotExistOptions): Cypress.Chainable<null>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add('consolelogin', { prevSubject: false }, (username: string, password: string) => {
|
Cypress.Commands.add('clipboardMatches', { prevSubject: false }, (pattern: RegExp | string) => {
|
||||||
|
/* doesn't work reliably
|
||||||
window.sessionStorage.removeItem("zitadel:access_token")
|
return cy.window()
|
||||||
cy.visit(Cypress.config('baseUrl')/ui/console).then(() => {
|
.then(win => {
|
||||||
// fill the fields and push button
|
win.focus()
|
||||||
cy.get('#loginName').type(username, { log: false })
|
return cy.waitUntil(() => win.navigator.clipboard.readText()
|
||||||
cy.get('#submit-button').click()
|
.then(clipboadText => {
|
||||||
cy.get('#password').type(password, { log: false })
|
win.focus()
|
||||||
cy.get('#submit-button').click()
|
const matches = typeof pattern === "string"
|
||||||
cy.location('pathname', {timeout: 5 * 1000}).should('eq', '/');
|
? clipboadText.includes(pattern)
|
||||||
|
: pattern.test(clipboadText)
|
||||||
|
if (!matches) {
|
||||||
|
cy.log(`text in clipboard ${clipboadText} doesn't match the pattern ${pattern}, yet`)
|
||||||
|
}
|
||||||
|
return matches
|
||||||
})
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
.then(() => null)
|
||||||
*/
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('shouldNotExist', { prevSubject: false }, (options?: ShouldNotExistOptions) => {
|
||||||
|
return cy.waitUntil(
|
||||||
|
() => {
|
||||||
|
return Cypress.$(options?.selector).length === 0;
|
||||||
|
},
|
||||||
|
{ timeout: typeof options?.timeout === 'number' ? options.timeout : 500 },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -43,7 +43,7 @@ services:
|
|||||||
network_mode: host
|
network_mode: host
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
image: cypress/included:10.3.0
|
image: cypress/included:10.9.0
|
||||||
depends_on:
|
depends_on:
|
||||||
zitadel:
|
zitadel:
|
||||||
condition: 'service_started'
|
condition: 'service_started'
|
||||||
|
57
e2e/package-lock.json
generated
57
e2e/package-lock.json
generated
@ -8,16 +8,17 @@
|
|||||||
"name": "zitadel-e2e",
|
"name": "zitadel-e2e",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cypress-wait-until": "^1.7.2",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mochawesome": "^7.1.3",
|
"mochawesome": "^7.1.3",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"typescript": "^4.8.3",
|
"typescript": "^4.8.4",
|
||||||
"wait-on": "^6.0.1"
|
"wait-on": "^6.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.7.13",
|
"@types/node": "^18.8.3",
|
||||||
"cypress": "^10.3.0"
|
"cypress": "^10.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@colors/colors": {
|
"node_modules/@colors/colors": {
|
||||||
@ -110,9 +111,9 @@
|
|||||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
|
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.7.13",
|
"version": "18.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.3.tgz",
|
||||||
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==",
|
"integrity": "sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/sinonjs__fake-timers": {
|
"node_modules/@types/sinonjs__fake-timers": {
|
||||||
@ -667,9 +668,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "10.3.0",
|
"version": "10.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.9.0.tgz",
|
||||||
"integrity": "sha512-txkQWKzvBVnWdCuKs5Xc08gjpO89W2Dom2wpZgT9zWZT5jXxqPIxqP/NC1YArtkpmp3fN5HW8aDjYBizHLUFvg==",
|
"integrity": "sha512-MjIWrRpc+bQM9U4kSSdATZWZ2hUqHGFEQTF7dfeZRa4MnalMtc88FIE49USWP2ZVtfy5WPBcgfBX+YorFqGElA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -692,7 +693,7 @@
|
|||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"debug": "^4.3.2",
|
"debug": "^4.3.2",
|
||||||
"enquirer": "^2.3.6",
|
"enquirer": "^2.3.6",
|
||||||
"eventemitter2": "^6.4.3",
|
"eventemitter2": "6.4.7",
|
||||||
"execa": "4.1.0",
|
"execa": "4.1.0",
|
||||||
"executable": "^4.1.1",
|
"executable": "^4.1.1",
|
||||||
"extract-zip": "2.0.1",
|
"extract-zip": "2.0.1",
|
||||||
@ -723,6 +724,11 @@
|
|||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cypress-wait-until": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q=="
|
||||||
|
},
|
||||||
"node_modules/cypress/node_modules/@types/node": {
|
"node_modules/cypress/node_modules/@types/node": {
|
||||||
"version": "14.18.26",
|
"version": "14.18.26",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.26.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.26.tgz",
|
||||||
@ -2559,9 +2565,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
||||||
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
|
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@ -2843,9 +2849,9 @@
|
|||||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
|
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "18.7.13",
|
"version": "18.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.3.tgz",
|
||||||
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==",
|
"integrity": "sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/sinonjs__fake-timers": {
|
"@types/sinonjs__fake-timers": {
|
||||||
@ -3251,9 +3257,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cypress": {
|
"cypress": {
|
||||||
"version": "10.3.0",
|
"version": "10.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.9.0.tgz",
|
||||||
"integrity": "sha512-txkQWKzvBVnWdCuKs5Xc08gjpO89W2Dom2wpZgT9zWZT5jXxqPIxqP/NC1YArtkpmp3fN5HW8aDjYBizHLUFvg==",
|
"integrity": "sha512-MjIWrRpc+bQM9U4kSSdATZWZ2hUqHGFEQTF7dfeZRa4MnalMtc88FIE49USWP2ZVtfy5WPBcgfBX+YorFqGElA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@cypress/request": "^2.88.10",
|
"@cypress/request": "^2.88.10",
|
||||||
@ -3275,7 +3281,7 @@
|
|||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"debug": "^4.3.2",
|
"debug": "^4.3.2",
|
||||||
"enquirer": "^2.3.6",
|
"enquirer": "^2.3.6",
|
||||||
"eventemitter2": "^6.4.3",
|
"eventemitter2": "6.4.7",
|
||||||
"execa": "4.1.0",
|
"execa": "4.1.0",
|
||||||
"executable": "^4.1.1",
|
"executable": "^4.1.1",
|
||||||
"extract-zip": "2.0.1",
|
"extract-zip": "2.0.1",
|
||||||
@ -3308,6 +3314,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cypress-wait-until": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q=="
|
||||||
|
},
|
||||||
"dashdash": {
|
"dashdash": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
@ -4663,9 +4674,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
||||||
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig=="
|
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ=="
|
||||||
},
|
},
|
||||||
"universalify": {
|
"universalify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
@ -4,22 +4,23 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"open": "npx cypress open",
|
"open": "npx cypress open",
|
||||||
"e2e": "npx cypress run",
|
"e2e": "npx cypress run",
|
||||||
"open:dev": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 npm run open",
|
"open:dev": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 npm run open --",
|
||||||
"e2e:dev": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 npm run e2e",
|
"e2e:dev": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 npm run e2e --",
|
||||||
"lint": "prettier --check cypress",
|
"lint": "prettier --check cypress",
|
||||||
"lint:fix": "prettier --write cypress"
|
"lint:fix": "prettier --write cypress"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cypress-wait-until": "^1.7.2",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mochawesome": "^7.1.3",
|
"mochawesome": "^7.1.3",
|
||||||
"wait-on": "^6.0.1",
|
"prettier": "^2.7.1",
|
||||||
"typescript": "^4.8.3",
|
"typescript": "^4.8.4",
|
||||||
"prettier": "^2.7.1"
|
"wait-on": "^6.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.7.13",
|
"@types/node": "^18.8.3",
|
||||||
"cypress": "^10.3.0"
|
"cypress": "^10.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user