ci(e2e): Run Tests in Pipelines (#3903)

* cy10 changes

* test: setup local e2e env

* test(e2e): migrate e2e setup

* add more config

* make e2e setup work

* align variables

* fix config

* skip mfa

* set user register to false

* read ids from database if not provided

* don't read ids withing env file

* fix escaping in id queries

* fix project root

* export projectRoot path

* export projectRoot

* add e2e-setup.sh

* specify GOOS and GOARCH for dockerfile compatible binary

* add org default redirect uri

* correctly initialize org policy

* await ids

* fix awaiting ids

* fix cypress configuration

* fix some tests

* initial compose setup

* fix working directory

* fix references

* make tests less flaky

* run go tests

* compose works until e2e-setup incl

* pass created e2e sa key

* make cypress run

* derive e2e orgs domain from baseurl

* use host from baseurl for setup ctx

* move defaults.yaml back to cmd pkg

* just create org owner

* Don't render element if no roles are passed

* use map instead of switchMap

* fix e2e tests

* added testdata for e3e

* zipped dump

* removed dumpDir

* cypress workflow with compose

* quote name

* cleanup vars

* eliminate need for e2e setup

* compose has no builds anymore

* use compose run and zitadel nw

* test e2e on pr (#4114)

* test e2e on pr

* install goreleaser

* install npm dev dependencies

* run cypress wf

* dynamic release version

* skip flaky user tests

* skip flaky permissions test

* cache docker layers in pipeline

* Update .github/workflows/cypress.yml

Co-authored-by: Florian Forster <florian@caos.ch>

* align goreleaser version

* get rid of install.sh

* remove cypress-terminal-report

* Revert "remove cypress-terminal-report"

This reverts commit 254b5a1f87.

* just one npm e2e:build command

* cache npm dependencies

* install node modules using docker

* dedicated e2e context

* fix syntax

* don't copy node modules from goreleaser

* add npm-copy target

* add tsconfig.json

* remove docker caching

* deleted unneeded shellscript

* naming and cleanup

Co-authored-by: Florian Forster <florian@caos.ch>
Co-authored-by: Christian Jakob <christian@caos.ch>

* cleanup

Co-authored-by: Elio Bischof <eliobischof@gmail.com>
Co-authored-by: Christian Jakob <christian@caos.ch>
Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
Max Peintner
2022-08-05 20:00:46 +02:00
committed by GitHub
parent ce85397050
commit fc99ec87c5
55 changed files with 5539 additions and 3621 deletions

View File

@@ -0,0 +1,15 @@
import { login, User } from 'support/login/users'
export interface apiCallProperties {
authHeader: string
mgntBaseURL: string
}
export function apiAuth(): Cypress.Chainable<apiCallProperties> {
return login(User.IAMAdminUser, 'Password1!', false, true).then(token => {
return <apiCallProperties>{
authHeader: `Bearer ${token}`,
mgntBaseURL: `/management/v1/`,
}
})
}

View File

@@ -0,0 +1,86 @@
import { apiCallProperties } from "./apiauth"
export function ensureSomethingExists(api: apiCallProperties, searchPath: string, find: (entity: any) => boolean, createPath: string, body: any): Cypress.Chainable<number> {
return searchSomething(api, searchPath, find).then(sRes => {
if (sRes.entity) {
return cy.wrap({
id: sRes.entity.id,
initialSequence: 0
})
}
return cy.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(30, (entity) => !!entity, data.initialSequence, api, searchPath, find)
return cy.wrap<number>(data.id)
})
}
export function ensureSomethingDoesntExist(api: apiCallProperties, searchPath: string, find: (entity: any) => boolean, deletePath: (entity: any) => string): Cypress.Chainable<null> {
return searchSomething(api, searchPath, find).then(sRes => {
if (!sRes.entity) {
return cy.wrap(0)
}
return cy.request({
method: 'DELETE',
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(30, (entity) => !entity , initialSequence, api, searchPath, find)
return null
})
}
type SearchResult = {
entity: any
sequence: number
}
function searchSomething(api: apiCallProperties, searchPath: string, find: (entity: any) => boolean): Cypress.Chainable<SearchResult> {
return cy.request({
method: 'POST',
url: `${api.mgntBaseURL}${searchPath}`,
headers: {
Authorization: api.authHeader
},
}).then(res => {
return {
entity: res.body.result?.find(find) || null,
sequence: res.body.details.processedSequence
}
})
}
function awaitDesired(trials: number, expectEntity: (entity: any) => boolean, initialSequence: number, api: apiCallProperties, searchPath: string, find: (entity: any) => boolean) {
searchSomething(api, searchPath, find).then(resp => {
if (!expectEntity(resp.entity) || resp.sequence <= initialSequence) {
expect(trials, `trying ${trials} more times`).to.be.greaterThan(0);
cy.wait(1000)
awaitDesired(trials - 1, expectEntity, initialSequence, api, searchPath, find)
}
})
}

View File

@@ -0,0 +1,19 @@
import { apiCallProperties } from "./apiauth"
export enum Policy {
Label = "label"
}
export function resetPolicy(api: apiCallProperties, policy: Policy) {
cy.request({
method: 'DELETE',
url: `${api.mgntBaseURL}/policies/${policy}`,
headers: {
Authorization: api.authHeader
},
}).then(res => {
expect(res.status).to.equal(200)
return null
})
}

View File

@@ -0,0 +1,80 @@
import { apiCallProperties } from "./apiauth"
import { ensureSomethingDoesntExist, ensureSomethingExists } from "./ensure"
export function ensureProjectExists(api: apiCallProperties, projectName: string): Cypress.Chainable<number> {
return ensureSomethingExists(
api,
`projects/_search`,
(project: any) => project.name === projectName,
'projects',
{ name: projectName },
)
}
export function ensureProjectDoesntExist(api: apiCallProperties, projectName: string): Cypress.Chainable<null> {
return ensureSomethingDoesntExist(
api,
`projects/_search`,
(project: any) => project.name === projectName,
(project) => `projects/${project.id}`,
)
}
class ResourceType {
constructor(
public resourcePath: string,
public compareProperty: string,
public identifierProperty: string,
){}
}
export const Apps = new ResourceType('apps', 'name', 'id')
export const Roles = new ResourceType('roles', 'key', 'key')
//export const Grants = new ResourceType('apps', 'name')
export function ensureProjectResourceDoesntExist(api: apiCallProperties, projectId: number, resourceType: ResourceType, resourceName: string): Cypress.Chainable<null> {
return ensureSomethingDoesntExist(
api,
`projects/${projectId}/${resourceType.resourcePath}/_search`,
(resource: any) => {
return resource[resourceType.compareProperty] === resourceName
},
(resource) => {
return `projects/${projectId}/${resourceType.resourcePath}/${resource[resourceType.identifierProperty]}`
}
)
}
export function ensureApplicationExists(api: apiCallProperties, projectId: number, appName: string): Cypress.Chainable<number> {
return ensureSomethingExists(
api,
`projects/${projectId}/${Apps.resourcePath}/_search`,
(resource: any) => resource.name === appName,
`projects/${projectId}/${Apps.resourcePath}/oidc`,
{
name: appName,
redirectUris: [
'https://e2eredirecturl.org'
],
responseTypes: [
"OIDC_RESPONSE_TYPE_CODE"
],
grantTypes: [
"OIDC_GRANT_TYPE_AUTHORIZATION_CODE"
],
authMethodType: "OIDC_AUTH_METHOD_TYPE_NONE",
postLogoutRedirectUris: [
'https://e2elogoutredirecturl.org'
],
/* "clientId": "129383004379407963@e2eprojectpermission",
"clockSkew": "0s",
"allowedOrigins": [
"https://testurl.org"
]*/
},
)
}

View File

@@ -0,0 +1,49 @@
import { apiCallProperties } from "./apiauth"
import { ensureSomethingDoesntExist, ensureSomethingExists } from "./ensure"
export function ensureHumanUserExists(api: apiCallProperties, username: string): Cypress.Chainable<number> {
return ensureSomethingExists(
api,
'users/_search',
(user: any) => user.userName === username,
'users/human',
{
user_name: username,
profile: {
first_name: 'e2efirstName',
last_name: 'e2elastName',
},
email: {
email: 'e2e@email.ch',
},
phone: {
phone: '+41 123456789',
},
})
}
export function ensureMachineUserExists(api: apiCallProperties, username: string): Cypress.Chainable<number> {
return ensureSomethingExists(
api,
'users/_search',
(user: any) => user.userName === username,
'users/machine',
{
user_name: username,
name: 'e2emachinename',
description: 'e2emachinedescription',
},
)
}
export function ensureUserDoesntExist(api: apiCallProperties, username: string): Cypress.Chainable<null> {
return ensureSomethingDoesntExist(
api,
'users/_search',
(user: any) => user.userName === username,
(user) => `users/${user.id}`
)
}