mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 13:13:38 +00:00
feat: add quotas (#4779)
adds possibilities to cap authenticated requests and execution seconds of actions on a defined intervall
This commit is contained in:
@@ -1,22 +1,26 @@
|
||||
import { Apps, ensureProjectExists, ensureProjectResourceDoesntExist } from '../../support/api/projects';
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { Context } from 'support/commands';
|
||||
|
||||
const testProjectName = 'e2eprojectapplication';
|
||||
const testAppName = 'e2eappundertest';
|
||||
|
||||
describe('applications', () => {
|
||||
const testProjectName = 'e2eprojectapplication';
|
||||
const testAppName = 'e2eappundertest';
|
||||
|
||||
beforeEach(() => {
|
||||
apiAuth()
|
||||
.as('api')
|
||||
.then((api) => {
|
||||
ensureProjectExists(api, testProjectName).as('projectId');
|
||||
cy.context()
|
||||
.as('ctx')
|
||||
.then((ctx) => {
|
||||
ensureProjectExists(ctx.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}`);
|
||||
describe('add app', () => {
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
cy.get<string>('@projectId').then((projectId) => {
|
||||
ensureProjectResourceDoesntExist(ctx.api, projectId, Apps, testAppName);
|
||||
cy.visit(`/projects/${projectId}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('add app', () => {
|
||||
|
@@ -1,25 +1,28 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { ensureHumanUserExists, ensureUserDoesntExist } from '../../support/api/users';
|
||||
import { loginname } from '../../support/login/users';
|
||||
import { ensureDomainPolicy } from '../../support/api/policies';
|
||||
import { Context } from 'support/commands';
|
||||
|
||||
describe('humans', () => {
|
||||
const humansPath = `/users?type=human`;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.context().as('ctx');
|
||||
});
|
||||
|
||||
[
|
||||
{ mustBeDomain: false, addName: 'e2ehumanusernameaddGlobal', removeName: 'e2ehumanusernameremoveGlobal' },
|
||||
{ mustBeDomain: false, addName: 'e2ehumanusernameadd@test.com', removeName: 'e2ehumanusernameremove@test.com' },
|
||||
{ mustBeDomain: true, addName: 'e2ehumanusernameadd', removeName: 'e2ehumanusernameremove' },
|
||||
// TODO:Changing the policy return 409 User already exists (SQL-M0dsf)
|
||||
// { mustBeDomain: true, addName: 'e2ehumanusernameadd', removeName: 'e2ehumanusernameremove' },
|
||||
].forEach((user) => {
|
||||
beforeEach(() => {
|
||||
apiAuth().as('api');
|
||||
});
|
||||
|
||||
describe(`add "${user.addName}" with domain setting "${user.mustBeDomain}"`, () => {
|
||||
beforeEach(`ensure it doesn't exist already`, function () {
|
||||
ensureDomainPolicy(this.api, user.mustBeDomain, true, false);
|
||||
ensureUserDoesntExist(this.api, user.addName);
|
||||
cy.visit(humansPath);
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureUserDoesntExist(ctx.api, user.addName);
|
||||
ensureDomainPolicy(ctx.api, user.mustBeDomain, true, false);
|
||||
cy.visit(humansPath);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a user', () => {
|
||||
@@ -44,19 +47,17 @@ describe('humans', () => {
|
||||
});
|
||||
|
||||
describe(`remove "${user.removeName}" with domain setting "${user.mustBeDomain}"`, () => {
|
||||
beforeEach('ensure it exists', function () {
|
||||
ensureHumanUserExists(this.api, user.removeName);
|
||||
beforeEach('ensure it exists', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureHumanUserExists(ctx.api, user.removeName);
|
||||
});
|
||||
cy.visit(humansPath);
|
||||
});
|
||||
|
||||
let loginName = user.removeName;
|
||||
if (user.mustBeDomain) {
|
||||
loginName = loginname(user.removeName, Cypress.env('ORGANIZATION'));
|
||||
}
|
||||
it('should delete a human user', () => {
|
||||
const rowSelector = `tr:contains(${user.removeName})`;
|
||||
cy.get(rowSelector).find('[data-e2e="enabled-delete-button"]').click({ force: true });
|
||||
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(loginName);
|
||||
cy.get('[data-e2e="confirm-dialog-input"]').focus().type(user.removeName);
|
||||
cy.get('[data-e2e="confirm-dialog-button"]').click();
|
||||
cy.shouldConfirmSuccess();
|
||||
cy.shouldNotExist({
|
||||
|
23
e2e/cypress/e2e/i18n/api.cy.ts
Normal file
23
e2e/cypress/e2e/i18n/api.cy.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { getInstance } from 'support/api/instances';
|
||||
import { ensureQuotaIsRemoved, removeQuota, Unit } from 'support/api/quota';
|
||||
import { Context } from 'support/commands';
|
||||
|
||||
describe('api internationalization', () => {
|
||||
beforeEach(() => {
|
||||
cy.context()
|
||||
.as('ctx')
|
||||
.then((ctx) => {
|
||||
ensureQuotaIsRemoved(ctx, Unit.ExecutionSeconds);
|
||||
});
|
||||
});
|
||||
it('instance not found error should be translated', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
removeQuota(ctx, Unit.ExecutionSeconds, false).then((res) => {
|
||||
expect(res.body.message).to.contain('Quota not found for this unit');
|
||||
});
|
||||
getInstance(ctx.system, "this ID clearly doesn't exist", false).then((res) => {
|
||||
expect(res.body.message).to.contain('Instance not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,25 +1,28 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { ensureMachineUserExists, ensureUserDoesntExist } from '../../support/api/users';
|
||||
import { loginname } from '../../support/login/users';
|
||||
import { ensureDomainPolicy } from '../../support/api/policies';
|
||||
import { Context } from 'support/commands';
|
||||
|
||||
describe('machines', () => {
|
||||
const machinesPath = `/users?type=machine`;
|
||||
|
||||
beforeEach(() => {
|
||||
apiAuth().as('api');
|
||||
cy.context().as('ctx');
|
||||
});
|
||||
|
||||
[
|
||||
{ mustBeDomain: false, addName: 'e2emachineusernameaddGlobal', removeName: 'e2emachineusernameremoveGlobal' },
|
||||
{ mustBeDomain: false, addName: 'e2emachineusernameadd@test.com', removeName: 'e2emachineusernameremove@test.com' },
|
||||
{ mustBeDomain: true, addName: 'e2emachineusernameadd', removeName: 'e2emachineusernameremove' },
|
||||
// TODO:Changing the policy return 409 User already exists (SQL-M0dsf)
|
||||
// { mustBeDomain: true, addName: 'e2emachineusernameadd', removeName: 'e2emachineusernameremove' },
|
||||
].forEach((machine) => {
|
||||
describe(`add "${machine.addName}" with domain setting "${machine.mustBeDomain}"`, () => {
|
||||
beforeEach(`ensure it doesn't exist already`, function () {
|
||||
ensureDomainPolicy(this.api, machine.mustBeDomain, false, false);
|
||||
ensureUserDoesntExist(this.api, machine.addName);
|
||||
cy.visit(machinesPath);
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureUserDoesntExist(ctx.api, machine.addName);
|
||||
ensureDomainPolicy(ctx.api, machine.mustBeDomain, false, false);
|
||||
cy.visit(machinesPath);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a machine', () => {
|
||||
@@ -41,9 +44,11 @@ describe('machines', () => {
|
||||
});
|
||||
|
||||
describe(`remove "${machine.removeName}" with domain setting "${machine.mustBeDomain}"`, () => {
|
||||
beforeEach('ensure it exists', function () {
|
||||
ensureMachineUserExists(this.api, machine.removeName);
|
||||
cy.visit(machinesPath);
|
||||
beforeEach('ensure it exists', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureMachineUserExists(ctx.api, machine.removeName);
|
||||
cy.visit(machinesPath);
|
||||
});
|
||||
});
|
||||
|
||||
let loginName = machine.removeName;
|
||||
|
@@ -1,26 +1,25 @@
|
||||
import { ensureOrgExists, ensureOrgIsDefault, isDefaultOrg } from 'support/api/orgs';
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Context } from 'support/commands';
|
||||
|
||||
const orgPath = `/org`;
|
||||
|
||||
const orgNameOnCreation = 'e2eorgrename';
|
||||
const testOrgNameChange = uuidv4();
|
||||
|
||||
beforeEach(() => {
|
||||
cy.context().as('ctx');
|
||||
});
|
||||
|
||||
describe('organizations', () => {
|
||||
describe('rename', () => {
|
||||
beforeEach(() => {
|
||||
apiAuth()
|
||||
.as('api')
|
||||
.then((api) => {
|
||||
ensureOrgExists(api, orgNameOnCreation)
|
||||
.as('newOrgId')
|
||||
.then((newOrgId) => {
|
||||
cy.visit(`${orgPath}?org=${newOrgId}`).as('orgsite');
|
||||
});
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureOrgExists(ctx, orgNameOnCreation).then((newOrgId) => {
|
||||
cy.visit(`${orgPath}?org=${newOrgId}`).as('orgsite');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should rename the organization', () => {
|
||||
cy.get('[data-e2e="actions"]').click();
|
||||
cy.get('[data-e2e="rename"]', { timeout: 1000 }).should('be.visible').click();
|
||||
@@ -31,24 +30,21 @@ describe('organizations', () => {
|
||||
cy.visit(orgPath);
|
||||
cy.get('[data-e2e="top-view-title"').should('contain', testOrgNameChange);
|
||||
});
|
||||
});
|
||||
|
||||
const orgOverviewPath = `/orgs`;
|
||||
const initialDefaultOrg = 'e2eorgolddefault';
|
||||
const orgNameForNewDefault = 'e2eorgnewdefault';
|
||||
const orgOverviewPath = `/orgs`;
|
||||
const initialDefaultOrg = 'e2eorgolddefault';
|
||||
const orgNameForNewDefault = 'e2eorgnewdefault';
|
||||
|
||||
describe('set default org', () => {
|
||||
beforeEach(() => {
|
||||
apiAuth()
|
||||
.as('api')
|
||||
.then((api) => {
|
||||
ensureOrgExists(api, orgNameForNewDefault)
|
||||
describe('set default org', () => {
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureOrgExists(ctx, orgNameForNewDefault)
|
||||
.as('newDefaultOrgId')
|
||||
.then(() => {
|
||||
ensureOrgExists(api, initialDefaultOrg)
|
||||
ensureOrgExists(ctx, initialDefaultOrg)
|
||||
.as('defaultOrg')
|
||||
.then((id) => {
|
||||
ensureOrgIsDefault(api, id)
|
||||
ensureOrgIsDefault(ctx, id)
|
||||
.as('orgWasDefault')
|
||||
.then(() => {
|
||||
cy.visit(`${orgOverviewPath}`).as('orgsite');
|
||||
@@ -56,19 +52,24 @@ describe('organizations', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should rename the organization', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
const rowSelector = `tr:contains(${orgNameForNewDefault})`;
|
||||
cy.get(rowSelector).find('[data-e2e="table-actions-button"]').click({ force: true });
|
||||
cy.get('[data-e2e="set-default-button"]', { timeout: 1000 }).should('be.visible').click();
|
||||
cy.shouldConfirmSuccess();
|
||||
cy.get<string>('@newDefaultOrgId').then((newDefaultOrgId) => {
|
||||
isDefaultOrg(ctx, newDefaultOrgId);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should rename the organization', function () {
|
||||
const rowSelector = `tr:contains(${orgNameForNewDefault})`;
|
||||
cy.get(rowSelector).find('[data-e2e="table-actions-button"]').click({ force: true });
|
||||
cy.get('[data-e2e="set-default-button"]', { timeout: 1000 }).should('be.visible').click();
|
||||
cy.shouldConfirmSuccess();
|
||||
isDefaultOrg(this.api, this.newDefaultOrgId);
|
||||
it('should add an organization with the personal account as org owner');
|
||||
describe('changing the current organization', () => {
|
||||
it('should update displayed organization details');
|
||||
});
|
||||
});
|
||||
|
||||
it('should add an organization with the personal account as org owner');
|
||||
describe('changing the current organization', () => {
|
||||
it('should update displayed organization details');
|
||||
});
|
||||
});
|
||||
|
@@ -6,36 +6,46 @@ import {
|
||||
ensureHumanIsProjectMember,
|
||||
} from 'support/api/members';
|
||||
import { ensureOrgExists } from 'support/api/orgs';
|
||||
import { ensureDomainPolicy } from 'support/api/policies';
|
||||
import { ensureHumanUserExists, ensureUserDoesntExist } from 'support/api/users';
|
||||
import { loginname } from 'support/login/users';
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { Context } from 'support/commands';
|
||||
import { ensureProjectExists, ensureProjectResourceDoesntExist, Roles } from '../../support/api/projects';
|
||||
|
||||
describe('permissions', () => {
|
||||
beforeEach(() => {
|
||||
apiAuth().as('api');
|
||||
cy.context()
|
||||
.as('ctx')
|
||||
.then((ctx) => {
|
||||
ensureDomainPolicy(ctx.api, false, true, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('management', () => {
|
||||
const testManagerLoginname = loginname('e2ehumanmanager', Cypress.env('ORGANIZATION'));
|
||||
const testManagerUsername = 'e2ehumanmanager';
|
||||
function testAuthorizations(
|
||||
roles: string[],
|
||||
beforeCreate: Mocha.HookFunction,
|
||||
beforeMutate: Mocha.HookFunction,
|
||||
navigate: Mocha.HookFunction,
|
||||
beforeCreate: (ctx: Context) => void,
|
||||
beforeMutate: (ctx: Context) => void,
|
||||
navigate: () => void,
|
||||
) {
|
||||
beforeEach(function () {
|
||||
ensureUserDoesntExist(this.api, testManagerLoginname);
|
||||
ensureHumanUserExists(this.api, testManagerLoginname);
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureUserDoesntExist(ctx.api, testManagerUsername);
|
||||
ensureHumanUserExists(ctx.api, testManagerUsername);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create authorization', () => {
|
||||
beforeEach(beforeCreate);
|
||||
beforeEach(navigate);
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
beforeCreate(ctx);
|
||||
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="add-member-input"]').type(testManagerUsername);
|
||||
cy.get('[data-e2e="user-option"]').first().click();
|
||||
cy.contains('[data-e2e="role-checkbox"]', roles[0]).click();
|
||||
cy.get('[data-e2e="confirm-add-member-button"]').click();
|
||||
@@ -45,14 +55,15 @@ describe('permissions', () => {
|
||||
});
|
||||
|
||||
describe('mutate authorization', () => {
|
||||
const rowSelector = `tr:contains(${testManagerLoginname})`;
|
||||
|
||||
beforeEach(beforeMutate);
|
||||
beforeEach(navigate);
|
||||
const rowSelector = `tr:contains(${testManagerUsername})`;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.contains('[data-e2e="member-avatar"]', 'ee').click();
|
||||
cy.get(rowSelector).as('managerRow');
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
beforeMutate(ctx);
|
||||
navigate();
|
||||
cy.contains('[data-e2e="member-avatar"]', 'ee').click();
|
||||
cy.get(rowSelector).as('managerRow');
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove a manager', () => {
|
||||
@@ -88,14 +99,14 @@ describe('permissions', () => {
|
||||
|
||||
testAuthorizations(
|
||||
roles.map((role) => role.display),
|
||||
function () {
|
||||
ensureHumanIsNotOrgMember(this.api, testManagerLoginname);
|
||||
function (ctx: Context) {
|
||||
ensureHumanIsNotOrgMember(ctx.api, testManagerUsername);
|
||||
},
|
||||
function () {
|
||||
ensureHumanIsNotOrgMember(this.api, testManagerLoginname);
|
||||
function (ctx: Context) {
|
||||
ensureHumanIsNotOrgMember(ctx.api, testManagerUsername);
|
||||
ensureHumanIsOrgMember(
|
||||
this.api,
|
||||
testManagerLoginname,
|
||||
ctx.api,
|
||||
testManagerUsername,
|
||||
roles.map((role) => role.internal),
|
||||
);
|
||||
},
|
||||
@@ -108,12 +119,16 @@ describe('permissions', () => {
|
||||
|
||||
describe('projects', () => {
|
||||
describe('owned projects', () => {
|
||||
beforeEach(function () {
|
||||
ensureProjectExists(this.api, 'e2eprojectpermission').as('projectId');
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureProjectExists(ctx.api, 'e2eprojectpermission').as('projectId');
|
||||
});
|
||||
});
|
||||
|
||||
const visitOwnedProject: Mocha.HookFunction = function () {
|
||||
cy.visit(`/projects/${this.projectId}`);
|
||||
const visitOwnedProject = () => {
|
||||
cy.get<number>('@projectId').then((projectId) => {
|
||||
cy.visit(`/projects/${projectId}`);
|
||||
});
|
||||
};
|
||||
|
||||
describe('authorizations', () => {
|
||||
@@ -124,17 +139,21 @@ describe('permissions', () => {
|
||||
|
||||
testAuthorizations(
|
||||
roles.map((role) => role.display),
|
||||
function () {
|
||||
ensureHumanIsNotProjectMember(this.api, this.projectId, testManagerLoginname);
|
||||
function (ctx) {
|
||||
cy.get<string>('@projectId').then((projectId) => {
|
||||
ensureHumanIsNotProjectMember(ctx.api, projectId, testManagerUsername);
|
||||
});
|
||||
},
|
||||
function () {
|
||||
ensureHumanIsNotProjectMember(this.api, this.projectId, testManagerLoginname);
|
||||
ensureHumanIsProjectMember(
|
||||
this.api,
|
||||
this.projectId,
|
||||
testManagerLoginname,
|
||||
roles.map((role) => role.internal),
|
||||
);
|
||||
function (ctx) {
|
||||
cy.get<string>('@projectId').then((projectId) => {
|
||||
ensureHumanIsNotProjectMember(ctx.api, projectId, testManagerUsername);
|
||||
ensureHumanIsProjectMember(
|
||||
ctx.api,
|
||||
projectId,
|
||||
testManagerUsername,
|
||||
roles.map((role) => role.internal),
|
||||
);
|
||||
});
|
||||
},
|
||||
visitOwnedProject,
|
||||
);
|
||||
@@ -143,12 +162,15 @@ describe('permissions', () => {
|
||||
describe('roles', () => {
|
||||
const testRoleName = 'e2eroleundertestname';
|
||||
|
||||
beforeEach(function () {
|
||||
ensureProjectResourceDoesntExist(this.api, this.projectId, Roles, testRoleName);
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
cy.get<string>('@projectId').then((projectId) => {
|
||||
ensureProjectResourceDoesntExist(ctx.api, projectId, Roles, testRoleName);
|
||||
visitOwnedProject();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(visitOwnedProject);
|
||||
|
||||
it('should add a role', () => {
|
||||
cy.get('[data-e2e="sidenav-element-roles"]').click();
|
||||
cy.get('[data-e2e="add-new-role"]').click();
|
||||
@@ -164,21 +186,25 @@ describe('permissions', () => {
|
||||
});
|
||||
|
||||
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');
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureOrgExists(ctx, 'e2eforeignorg').then((foreignOrgId) => {
|
||||
ensureProjectExists(ctx.api, 'e2eprojectgrants', foreignOrgId)
|
||||
.as('foreignProjectId')
|
||||
.then((foreignProjectId) => {
|
||||
ensureProjectGrantExists(ctx, foreignOrgId, foreignProjectId).as('grantId');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const visitGrantedProject: Mocha.HookFunction = function () {
|
||||
cy.visit(`/granted-projects/${this.projectId}/grant/${this.grantId}`);
|
||||
};
|
||||
function visitGrantedProject() {
|
||||
cy.get<string>('@foreignProjectId').then((foreignProjectId) => {
|
||||
cy.get<string>('@grantId').then((grantId) => {
|
||||
cy.visit(`/granted-projects/${foreignProjectId}/grant/${grantId}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('authorizations', () => {
|
||||
const roles = [
|
||||
@@ -188,18 +214,26 @@ describe('permissions', () => {
|
||||
|
||||
testAuthorizations(
|
||||
roles.map((role) => role.display),
|
||||
function () {
|
||||
ensureHumanIsNotProjectMember(this.api, this.projectId, testManagerLoginname, this.grantId);
|
||||
function (ctx: Context) {
|
||||
cy.get<string>('@foreignProjectId').then((foreignProjectId) => {
|
||||
cy.get<string>('@grantId').then((grantId) => {
|
||||
ensureHumanIsNotProjectMember(ctx.api, foreignProjectId, testManagerUsername, grantId);
|
||||
});
|
||||
});
|
||||
},
|
||||
function () {
|
||||
ensureHumanIsNotProjectMember(this.api, this.projectId, testManagerLoginname, this.grantId);
|
||||
ensureHumanIsProjectMember(
|
||||
this.api,
|
||||
this.projectId,
|
||||
testManagerLoginname,
|
||||
roles.map((role) => role.internal),
|
||||
this.grantId,
|
||||
);
|
||||
function (ctx: Context) {
|
||||
cy.get<string>('@foreignProjectId').then((foreignProjectId) => {
|
||||
cy.get<string>('@grantId').then((grantId) => {
|
||||
ensureHumanIsNotProjectMember(ctx.api, foreignProjectId, testManagerUsername, grantId);
|
||||
ensureHumanIsProjectMember(
|
||||
ctx.api,
|
||||
foreignProjectId,
|
||||
testManagerUsername,
|
||||
roles.map((role) => role.internal),
|
||||
grantId,
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
visitGrantedProject,
|
||||
);
|
||||
@@ -207,42 +241,42 @@ describe('permissions', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validations', () => {
|
||||
describe('owned projects', () => {
|
||||
describe('no ownership', () => {
|
||||
it('a user without project global ownership can ...');
|
||||
it('a user without project global ownership can not ...');
|
||||
});
|
||||
describe('project owner viewer global', () => {
|
||||
it('a project owner viewer global additionally can ...');
|
||||
it('a project owner viewer global still can not ...');
|
||||
});
|
||||
describe('project owner global', () => {
|
||||
it('a project owner global additionally can ...');
|
||||
it('a project owner global still can not ...');
|
||||
});
|
||||
describe('validations', () => {
|
||||
describe('owned projects', () => {
|
||||
describe('no ownership', () => {
|
||||
it('a user without project global ownership can ...');
|
||||
it('a user without project global ownership can not ...');
|
||||
});
|
||||
describe('project owner viewer global', () => {
|
||||
it('a project owner viewer global additionally can ...');
|
||||
it('a project owner viewer global still can not ...');
|
||||
});
|
||||
describe('project owner global', () => {
|
||||
it('a project owner global additionally can ...');
|
||||
it('a project owner global still can not ...');
|
||||
});
|
||||
});
|
||||
|
||||
describe('granted projects', () => {
|
||||
describe('no ownership', () => {
|
||||
it('a user without project grant ownership can ...');
|
||||
it('a user without project grant ownership can not ...');
|
||||
});
|
||||
describe('project grant owner viewer', () => {
|
||||
it('a project grant owner viewer additionally can ...');
|
||||
it('a project grant owner viewer still can not ...');
|
||||
});
|
||||
describe('project grant owner', () => {
|
||||
it('a project grant owner additionally can ...');
|
||||
it('a project grant owner still can not ...');
|
||||
});
|
||||
describe('granted projects', () => {
|
||||
describe('no ownership', () => {
|
||||
it('a user without project grant ownership can ...');
|
||||
it('a user without project grant ownership can not ...');
|
||||
});
|
||||
describe('organization', () => {
|
||||
describe('org owner', () => {
|
||||
it('a project owner global can ...');
|
||||
it('a project owner global can not ...');
|
||||
});
|
||||
describe('project grant owner viewer', () => {
|
||||
it('a project grant owner viewer additionally can ...');
|
||||
it('a project grant owner viewer still can not ...');
|
||||
});
|
||||
describe('project grant owner', () => {
|
||||
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 ...');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,18 +1,20 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { Context } from 'support/commands';
|
||||
import { ensureProjectDoesntExist, ensureProjectExists } from '../../support/api/projects';
|
||||
|
||||
describe('projects', () => {
|
||||
beforeEach(() => {
|
||||
apiAuth().as('api');
|
||||
cy.context().as('ctx');
|
||||
});
|
||||
|
||||
const testProjectNameCreate = 'e2eprojectcreate';
|
||||
const testProjectNameDelete = 'e2eprojectdelete';
|
||||
|
||||
describe('add project', () => {
|
||||
beforeEach(`ensure it doesn't exist already`, function () {
|
||||
ensureProjectDoesntExist(this.api, testProjectNameCreate);
|
||||
cy.visit(`/projects`);
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureProjectDoesntExist(ctx.api, testProjectNameCreate);
|
||||
cy.visit(`/projects`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a project', () => {
|
||||
@@ -26,9 +28,11 @@ describe('projects', () => {
|
||||
});
|
||||
|
||||
describe('edit project', () => {
|
||||
beforeEach('ensure it exists', function () {
|
||||
ensureProjectExists(this.api, testProjectNameDelete);
|
||||
cy.visit(`/projects`);
|
||||
beforeEach('ensure it exists', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureProjectExists(ctx.api, testProjectNameDelete);
|
||||
cy.visit(`/projects`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove project', () => {
|
||||
|
248
e2e/cypress/e2e/quotas/quotas.cy.ts
Normal file
248
e2e/cypress/e2e/quotas/quotas.cy.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import { addQuota, ensureQuotaIsAdded, ensureQuotaIsRemoved, removeQuota, Unit } from 'support/api/quota';
|
||||
import { createHumanUser, ensureUserDoesntExist } from 'support/api/users';
|
||||
import { Context } from 'support/commands';
|
||||
import { ZITADELWebhookEvent } from 'support/types';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.context().as('ctx');
|
||||
});
|
||||
|
||||
describe('quotas', () => {
|
||||
describe('management', () => {
|
||||
describe('add one quota', () => {
|
||||
it('should add a quota only once per unit', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
addQuota(ctx, Unit.AuthenticatedRequests, true, 1);
|
||||
addQuota(ctx, Unit.AuthenticatedRequests, true, 1, undefined, undefined, undefined, false).then((res) => {
|
||||
expect(res.status).to.equal(409);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('add two quotas', () => {
|
||||
it('should add a quota for each unit', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
addQuota(ctx, Unit.AuthenticatedRequests, true, 1);
|
||||
addQuota(ctx, Unit.ExecutionSeconds, true, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit', () => {
|
||||
describe('remove one quota', () => {
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureQuotaIsAdded(ctx, Unit.AuthenticatedRequests, true, 1);
|
||||
});
|
||||
});
|
||||
it('should remove a quota only once per unit', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
removeQuota(ctx, Unit.AuthenticatedRequests);
|
||||
});
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
removeQuota(ctx, Unit.AuthenticatedRequests, false).then((res) => {
|
||||
expect(res.status).to.equal(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove two quotas', () => {
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureQuotaIsAdded(ctx, Unit.AuthenticatedRequests, true, 1);
|
||||
ensureQuotaIsAdded(ctx, Unit.ExecutionSeconds, true, 1);
|
||||
});
|
||||
});
|
||||
it('should remove a quota for each unit', () => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
removeQuota(ctx, Unit.AuthenticatedRequests);
|
||||
removeQuota(ctx, Unit.ExecutionSeconds);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('usage', () => {
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx')
|
||||
.then((ctx) => {
|
||||
return [
|
||||
`${ctx.api.oidcBaseURL}/userinfo`,
|
||||
`${ctx.api.authBaseURL}/users/me`,
|
||||
`${ctx.api.mgmtBaseURL}/iam`,
|
||||
`${ctx.api.adminBaseURL}/instances/me`,
|
||||
`${ctx.api.oauthBaseURL}/keys`,
|
||||
`${ctx.api.samlBaseURL}/certificate`,
|
||||
];
|
||||
})
|
||||
.as('authenticatedUrls');
|
||||
});
|
||||
|
||||
describe('authenticated requests', () => {
|
||||
const testUserName = 'shouldNotBeCreated';
|
||||
beforeEach(() => {
|
||||
cy.get<Array<string>>('@authenticatedUrls').then((urls) => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureUserDoesntExist(ctx.api, testUserName);
|
||||
ensureQuotaIsAdded(ctx, Unit.AuthenticatedRequests, true, urls.length);
|
||||
cy.task('runSQL', `TRUNCATE logstore.access;`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('authenticated requests are limited', () => {
|
||||
cy.get<Array<string>>('@authenticatedUrls').then((urls) => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
const start = new Date();
|
||||
urls.forEach((url) => {
|
||||
cy.request({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
auth: {
|
||||
bearer: ctx.api.token,
|
||||
},
|
||||
});
|
||||
});
|
||||
const expiresMax = new Date();
|
||||
expiresMax.setMinutes(expiresMax.getMinutes() + 2);
|
||||
cy.getCookie('zitadel.quota.limiting').then((cookie) => {
|
||||
expect(cookie.value).to.equal('false');
|
||||
const cookieExpiry = new Date();
|
||||
cookieExpiry.setTime(cookie.expiry * 1000);
|
||||
expect(cookieExpiry).to.be.within(start, expiresMax);
|
||||
});
|
||||
cy.request({
|
||||
url: urls[0],
|
||||
method: 'GET',
|
||||
auth: {
|
||||
bearer: ctx.api.token,
|
||||
},
|
||||
failOnStatusCode: false,
|
||||
}).then((res) => {
|
||||
expect(res.status).to.equal(429);
|
||||
});
|
||||
cy.getCookie('zitadel.quota.limiting').then((cookie) => {
|
||||
expect(cookie.value).to.equal('true');
|
||||
});
|
||||
createHumanUser(ctx.api, testUserName, false).then((res) => {
|
||||
expect(res.status).to.equal(429);
|
||||
});
|
||||
ensureQuotaIsRemoved(ctx, Unit.AuthenticatedRequests);
|
||||
createHumanUser(ctx.api, testUserName);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('notifications', () => {
|
||||
const callURL = `http://${Cypress.env('WEBHOOK_HANDLER_HOST')}:${Cypress.env('WEBHOOK_HANDLER_PORT')}/do_something`;
|
||||
|
||||
beforeEach(() => cy.task('resetWebhookEvents'));
|
||||
|
||||
const amount = 100;
|
||||
const percent = 10;
|
||||
const usage = 25;
|
||||
|
||||
describe('without repetition', () => {
|
||||
beforeEach(() => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureQuotaIsAdded(ctx, Unit.AuthenticatedRequests, false, amount, [
|
||||
{
|
||||
callUrl: callURL,
|
||||
percent: percent,
|
||||
repeat: false,
|
||||
},
|
||||
]);
|
||||
cy.task('runSQL', `TRUNCATE logstore.access;`);
|
||||
});
|
||||
});
|
||||
|
||||
it('fires once with the expected payload', () => {
|
||||
cy.get<Array<string>>('@authenticatedUrls').then((urls) => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
for (let i = 0; i < usage; i++) {
|
||||
cy.request({
|
||||
url: urls[0],
|
||||
method: 'GET',
|
||||
auth: {
|
||||
bearer: ctx.api.token,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
cy.waitUntil(() =>
|
||||
cy.task<Array<ZITADELWebhookEvent>>('handledWebhookEvents').then((events) => {
|
||||
if (events.length != 1) {
|
||||
return false;
|
||||
}
|
||||
return Cypress._.matches(<ZITADELWebhookEvent>{
|
||||
callURL: callURL,
|
||||
threshold: percent,
|
||||
unit: 1,
|
||||
usage: percent,
|
||||
})(events[0]);
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with repetition', () => {
|
||||
beforeEach(() => {
|
||||
cy.get<Array<string>>('@authenticatedUrls').then((urls) => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
ensureQuotaIsAdded(ctx, Unit.AuthenticatedRequests, false, amount, [
|
||||
{
|
||||
callUrl: callURL,
|
||||
percent: percent,
|
||||
repeat: true,
|
||||
},
|
||||
]);
|
||||
cy.task('runSQL', `TRUNCATE logstore.access;`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fires repeatedly with the expected payloads', () => {
|
||||
cy.get<Array<string>>('@authenticatedUrls').then((urls) => {
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
for (let i = 0; i < usage; i++) {
|
||||
cy.request({
|
||||
url: urls[0],
|
||||
method: 'GET',
|
||||
auth: {
|
||||
bearer: ctx.api.token,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
cy.waitUntil(() =>
|
||||
cy.task<Array<ZITADELWebhookEvent>>('handledWebhookEvents').then((events) => {
|
||||
if (events.length != 1) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const threshold = percent * (i + 1);
|
||||
if (
|
||||
!Cypress._.matches(<ZITADELWebhookEvent>{
|
||||
callURL: callURL,
|
||||
threshold: threshold,
|
||||
unit: 1,
|
||||
usage: threshold,
|
||||
})(events[i])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,4 +1,5 @@
|
||||
import { apiAuth, API } from '../../support/api/apiauth';
|
||||
import { Context } from 'mocha';
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { Policy, resetPolicy } from '../../support/api/policies';
|
||||
import { login, User } from '../../support/login/users';
|
||||
|
||||
@@ -7,8 +8,6 @@ describe('private labeling', () => {
|
||||
|
||||
[User.OrgOwner].forEach((user) => {
|
||||
describe(`as user "${user}"`, () => {
|
||||
let api: API;
|
||||
|
||||
beforeEach(() => {
|
||||
login(user);
|
||||
cy.visit(orgPath);
|
||||
@@ -37,34 +36,27 @@ function customize(theme: string, user: User) {
|
||||
});
|
||||
|
||||
it('should update a logo', () => {
|
||||
cy.get('[data-e2e="image-part-logo"]')
|
||||
.find('input')
|
||||
.then(function (el) {
|
||||
const blob = Cypress.Blob.base64StringToBlob(this.logo, 'image/png');
|
||||
const file = new File([blob], 'images/logo.png', { type: 'image/png' });
|
||||
const list = new DataTransfer();
|
||||
cy.get<Context>('@ctx').then((ctx) => {
|
||||
cy.get('[data-e2e="image-part-logo"]')
|
||||
.find('input')
|
||||
.then(function (el) {
|
||||
const blob = Cypress.Blob.base64StringToBlob(ctx.logo, 'image/png');
|
||||
const file = new File([blob], 'images/logo.png', { type: 'image/png' });
|
||||
const list = new DataTransfer();
|
||||
|
||||
list.items.add(file);
|
||||
const myFileList = list.files;
|
||||
list.items.add(file);
|
||||
const myFileList = list.files;
|
||||
|
||||
el[0].files = myFileList;
|
||||
el[0].dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
el[0].files = myFileList;
|
||||
el[0].dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should delete a logo');
|
||||
});
|
||||
it('should update an icon');
|
||||
it('should delete an icon');
|
||||
it.skip('should update the background color', () => {
|
||||
cy.contains('[data-e2e="color"]', 'Background Color').find('button').click(); // TODO: select data-e2e
|
||||
cy.get('color-editable-input').find('input').clear().type('#ae44dc');
|
||||
cy.get('[data-e2e="save-colors-button"]').click();
|
||||
cy.get('[data-e2e="header-user-avatar"]').click();
|
||||
cy.contains('Logout All Users').click(); // TODO: select data-e2e
|
||||
login(User.LoginPolicyUser, undefined, true, null, () => {
|
||||
cy.pause();
|
||||
});
|
||||
});
|
||||
it('should update the background color');
|
||||
it('should update the primary color');
|
||||
it('should update the warning color');
|
||||
it('should update the font color');
|
||||
|
Reference in New Issue
Block a user