Skip to content

Commit

Permalink
feat: ✨ add backoffice for admins
Browse files Browse the repository at this point in the history
  • Loading branch information
clairenollet committed Dec 11, 2023
1 parent ef7ffdb commit fadfdfc
Show file tree
Hide file tree
Showing 30 changed files with 1,252 additions and 250 deletions.
115 changes: 115 additions & 0 deletions apps/client/cypress/components/specs/team-ct.ct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Pinia, createPinia, setActivePinia } from 'pinia'
import '@gouvminint/vue-dsfr/styles'
import '@gouvfr/dsfr/dist/dsfr.min.css'
import '@gouvfr/dsfr/dist/utility/icons/icons.min.css'
import '@gouvfr/dsfr/dist/utility/utility.main.min.css'
import '@/main.css'
import TeamCt from '@/components/TeamCt.vue'
import { createRandomDbSetup, getRandomUser } from '@dso-console/test-utils'
import { useProjectStore } from '@/stores/project.js'

describe('TeamCt.vue', () => {
let pinia: Pinia

beforeEach(() => {
pinia = createPinia()

setActivePinia(pinia)
})

it('Should mount a TeamCt for owner', () => {
useProjectStore()
const randomDbSetup = createRandomDbSetup({ nbUsers: 4 })
const owner = randomDbSetup.project.roles.find(role => role.role === 'owner')?.user
const newUser = getRandomUser()

cy.intercept('GET', `api/v1/projects/${randomDbSetup.project.id}/users/match?letters=*`, { body: [newUser] })

const props = {
userProfile: {
...owner,
groups: [],
},
project: randomDbSetup.project,
owner,
}

cy.mount(TeamCt, { props })

cy.getByDataTestid('teamTable').should('be.visible')
.within(() => {
cy.get('caption')
.should('contain', `Membres du projet ${randomDbSetup.project.name}`)
cy.get('tbody > tr')
.should('have.length', randomDbSetup.project.roles?.length)
cy.get(`td[title="${owner?.email} ne peut pas être retiré(e) du projet"]`)
.should('have.class', 'disabled')
cy.get('td[title^="retirer"]')
.should('not.have.class', 'disabled')
})
})
it('Should mount a TeamCt for admin', () => {
useProjectStore()
const randomDbSetup = createRandomDbSetup({ nbUsers: 4 })
const owner = randomDbSetup.project.roles.find(role => role.role === 'owner')?.user
const user = randomDbSetup.project.roles.find(role => role.role !== 'owner')?.user
const newUser = getRandomUser()

cy.intercept('GET', `api/v1/projects/${randomDbSetup.project.id}/users/match?letters=*`, { body: [newUser] })

const props = {
userProfile: {
...user,
groups: ['/admin'],
},
project: randomDbSetup.project,
owner,
}

cy.mount(TeamCt, { props })

cy.getByDataTestid('teamTable').should('be.visible')
.within(() => {
cy.get('caption')
.should('contain', `Membres du projet ${randomDbSetup.project.name}`)
cy.get('tbody > tr')
.should('have.length', randomDbSetup.project.roles?.length)
cy.get(`td[title="${owner?.email} ne peut pas être retiré(e) du projet"]`)
.should('have.class', 'disabled')
cy.get('td[title^="retirer"]')
.should('not.have.class', 'disabled')
})
})
it('Should mount a TeamCt for user', () => {
useProjectStore()
const randomDbSetup = createRandomDbSetup({ nbUsers: 4 })
const owner = randomDbSetup.project.roles.find(role => role.role === 'owner')?.user
const user = randomDbSetup.project.roles.find(role => role.role !== 'owner')?.user
const newUser = getRandomUser()

cy.intercept('GET', `api/v1/projects/${randomDbSetup.project.id}/users/match?letters=*`, { body: [newUser] })

const props = {
userProfile: {
...user,
groups: [],
},
project: randomDbSetup.project,
owner,
}

cy.mount(TeamCt, { props })

cy.getByDataTestid('teamTable').should('be.visible')
.within(() => {
cy.get('caption')
.should('contain', `Membres du projet ${randomDbSetup.project.name}`)
cy.get('tbody > tr')
.should('have.length', randomDbSetup.project.roles?.length)
cy.get(`td[title="${owner?.email} ne peut pas être retiré(e) du projet"]`)
.should('have.class', 'disabled')
cy.get('td[title="vous n\'avez pas les droits suffisants pour retirer un membre du projet"]')
.should('have.class', 'disabled')
})
})
})
230 changes: 215 additions & 15 deletions apps/client/cypress/e2e/specs/admin/projects.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { getModel } from '../../support/func'
import { getModel, getModelById } from '../../support/func'
import { statusDict, formatDate, sortArrByObjKeyAsc, OrganizationModel, ProjectModel } from '@dso-console/shared'

describe('Administration projects', () => {
const admin = getModelById('user', 'cb8e5b4b-7b7b-40f5-935f-594f48ae6566')
const organizations = getModel('organization') as OrganizationModel[]
let projects: (Pick<ProjectModel, 'name' | 'description' | 'locked' | 'status'> & { organization: string, owner: { email: string }, createdAt: string, updatedAt: string })[]
let projects: unknown[]

const mapProjects = (body: ProjectModel[]) => {
return sortArrByObjKeyAsc(body, 'name')
?.map(project => ({
...project,
owner: project.roles?.find(role => role.role === 'owner')?.user,
organization: organizations.find(organization => organization.id === project.organizationId).label,
}),
)
}

beforeEach(() => {
cy.intercept('GET', 'api/v1/admin/projects').as('getAllProjects')

cy.kcLogin('tcolin')
cy.kcLogin((admin.firstName.slice(0, 1) + admin.lastName).toLowerCase())
cy.visit('/admin/projects')
cy.url().should('contain', '/admin/projects')
cy.wait('@getAllProjects', { timeout: 10000 }).its('response').then(response => {
projects = sortArrByObjKeyAsc(response.body as ProjectModel[], 'name')
?.map(project => ({
organization: organizations.find(organization => organization.id === project.organizationId).label,
name: project.name,
description: project.description ?? '',
owner: project.roles[0].user,
status: project.status,
locked: project.locked,
createdAt: project.createdAt,
updatedAt: project.updatedAt,
}),
)
projects = mapProjects(response.body)
})
})

Expand All @@ -43,4 +43,204 @@ describe('Administration projects', () => {
})
})
})

it('Should lock and unlock a project, loggedIn as admin', () => {
const project = projects[0]

cy.intercept('GET', 'api/v1/quotas').as('getQuotas')
cy.intercept('PATCH', `api/v1/admin/projects/${project.id}`).as('handleProjectLocking')

cy.getByDataTestid('tableAdministrationProjects').within(() => {
cy.get('tr').contains(project.name)
.click()
})
cy.wait('@getQuotas')
cy.get('.fr-callout__title')
.should('contain', project.name)
cy.getByDataTestid('handleProjectLockingBtn')
.should('contain', project.locked ? 'Déverrouiller le projet' : 'Verrouiller le projet')
.click()
cy.wait('@handleProjectLocking')
.its('response.statusCode')
.should('eq', 204)
cy.getByDataTestid('handleProjectLockingBtn')
.should('contain', 'Déverrouiller le projet')
.click()
cy.wait('@handleProjectLocking')
.its('response.statusCode')
.should('eq', 204)
})

it('Should archive a project, loggedIn as admin', () => {
cy.intercept('GET', 'api/v1/admin/projects').as('getAllProjects')
cy.intercept('GET', 'api/v1/quotas').as('getQuotas')
cy.intercept('DELETE', 'api/v1/projects/*').as('archiveProject')

const projectName = 'admin-archive'

cy.createProject({ name: projectName }, admin?.email)

cy.visit('/admin/projects')
cy.url().should('contain', '/admin/projects')
cy.wait('@getAllProjects')

cy.getByDataTestid('tableAdministrationProjects').within(() => {
cy.get('tr').contains(projectName)
.click()
})
cy.wait('@getQuotas')
cy.get('.fr-callout__title')
.should('contain', projectName)
cy.getByDataTestid('archiveProjectInput').should('not.exist')
.getByDataTestid('showArchiveProjectBtn').click()
.getByDataTestid('archiveProjectBtn')
.should('be.disabled')
.getByDataTestid('archiveProjectInput').should('be.visible')
.type(projectName)
.getByDataTestid('archiveProjectBtn')
.should('be.enabled')
.click()
cy.wait('@archiveProject')
.its('response.statusCode')
.should('eq', 204)
cy.getByDataTestid('tableAdministrationProjects')
.should('be.visible')
.within(() => {
cy.get('tr').contains(projectName)
.click()
})
cy.getByDataTestid('snackbar').within(() => {
cy.get('p').should('contain', 'Le projet est archivé, pas d\'action possible')
})
cy.getByDataTestid('tableAdministrationProjects')
.should('be.visible')
})

it('Should update an environment quota, loggedIn as admin', () => {
const project = projects.find(project => project.name === 'beta-app')
const privateQuota = {
id: '',
name: 'XXXXLprivate',
memory: '20Gi',
cpu: '5',
stages: [getModelById('stage', project.environments[0].quotaStage.stage.id)],
}
const initialQuota = project.environments[0].quotaStage.quota.id

cy.intercept('GET', 'api/v1/admin/projects').as('getAllProjects')
cy.intercept('GET', 'api/v1/projects').as('getProjects')
cy.intercept('GET', 'api/v1/quotas').as('getQuotas')
cy.intercept('GET', 'api/v1/stages').as('getStages')
cy.intercept('POST', '/api/v1/admin/quotas').as('createQuota')
cy.intercept('PUT', `api/v1/projects/${project.id}/environments/*`).as('updateEnvironment')

cy.visit('/admin/quotas')
cy.url().should('contain', '/admin/quotas')
cy.wait('@getQuotas')
cy.wait('@getStages')
cy.getByDataTestid('addQuotaLink')
.should('be.visible')
.click()
cy.getByDataTestid('nameInput')
.find('input')
.type(privateQuota.name)
cy.getByDataTestid('memoryInput')
.find('input')
.type(privateQuota.memory)
cy.getByDataTestid('cpuInput')
.find('input')
.type(privateQuota.cpu)
cy.getByDataTestid('isQuotaPrivateCbx').find('input[type=checkbox]')
.check({ force: true })
cy.getByDataTestid('addQuotaBtn').should('be.enabled')
privateQuota.stages.forEach(stage => {
cy.get('#stages-select')
.should('be.enabled')
.select(stage.name)
})
cy.getByDataTestid('addQuotaBtn')
.click()

cy.wait('@createQuota').then(({ response }) => {
privateQuota.id = response.body?.id
expect(response.statusCode).to.equal(201)

cy.visit('/admin/projects')
cy.url().should('contain', '/admin/projects')
cy.wait('@getAllProjects', { timeout: 10000 }).its('response').then(pResponse => {
projects = mapProjects(pResponse.body)
})

cy.getByDataTestid('tableAdministrationProjects').within(() => {
cy.get('tr').contains(project.name)
.click()
})
cy.wait('@getQuotas')
cy.get('.fr-callout__title')
.should('contain', project.name)
cy.get('select#quota-select:first')
.should('have.value', initialQuota)
.and('be.enabled')
cy.get('select#quota-select:first')
.select(privateQuota.id)
cy.wait('@updateEnvironment')
.its('response.statusCode')
.should('eq', 200)
cy.wait('@getAllProjects')
cy.wait('@getQuotas')
cy.get('select#quota-select:first')
.should('have.value', privateQuota.id)
.and('be.enabled')
cy.reload()
cy.getByDataTestid('tableAdministrationProjects').within(() => {
cy.get('tr').contains(project.name)
.click()
})
cy.wait('@getQuotas')
cy.get('select#quota-select:first')
.select(initialQuota)
cy.wait('@updateEnvironment')
.its('response.statusCode')
.should('eq', 200)
cy.wait('@getProjects')
cy.wait('@getAllProjects')
cy.wait('@getQuotas')
})
})

it('Should remove and add a user from a project, loggedIn as admin', () => {
const project = projects.find(project => project.name === 'beta-app')
const user = project.roles.find(role => role.role !== 'owner')?.user

cy.intercept('GET', 'api/v1/admin/projects').as('getAllProjects')
cy.intercept('GET', 'api/v1/quotas').as('getQuotas')
cy.intercept('DELETE', `api/v1/projects/${project.id}/users/${user.id}`).as('removeUser')
cy.intercept('POST', `api/v1/projects/${project.id}/users`).as('addUser')

cy.getByDataTestid('tableAdministrationProjects').within(() => {
cy.get('tr').contains(project.name)
.click()
})
cy.wait('@getQuotas')
cy.get('.fr-callout__title')
.should('contain', project.name)
cy.get(`td[title="retirer ${user.email} du projet"]`)
.click()
cy.wait('@removeUser')
.its('response.statusCode')
.should('eq', 200)
cy.get(`td[title="retirer ${user.email} du projet"]`)
.should('not.exist')
cy.getByDataTestid('addUserSuggestionInput')
.find('input')
.clear()
.type(user.email)
cy.getByDataTestid('addUserBtn')
.click()
cy.wait('@addUser')
.its('response.statusCode')
.should('eq', 201)
cy.get(`td[title="retirer ${user.email} du projet"]`)
.should('exist')
})
})
Loading

0 comments on commit fadfdfc

Please sign in to comment.