diff --git a/cypress.config.ts b/cypress.config.ts index 4b01931b..c1100467 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -8,5 +8,6 @@ export default defineConfig({ env: { umami_user: 'admin', umami_password: 'umami', + umami_user_id: '41e2b680-648e-4b09-bcd7-3e2b10c06264', }, }); diff --git a/cypress/e2e/api-team.cy.ts b/cypress/e2e/api-team.cy.ts new file mode 100644 index 00000000..22811130 --- /dev/null +++ b/cypress/e2e/api-team.cy.ts @@ -0,0 +1,209 @@ +describe('Team API tests', () => { + Cypress.session.clearAllSavedSessions(); + + let teamId; + let userId; + + before(() => { + cy.login(Cypress.env('umami_user'), Cypress.env('umami_password')); + cy.fixture('users').then(data => { + const userCreate = data.userCreate; + cy.request({ + method: 'POST', + url: '/api/users', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: userCreate, + }).then(response => { + userId = response.body.id; + expect(response.status).to.eq(200); + expect(response.body).to.have.property('username', 'cypress1'); + expect(response.body).to.have.property('role', 'user'); + }); + }); + }); + + it('Creates a team.', () => { + cy.fixture('teams').then(data => { + const teamCreate = data.teamCreate; + cy.request({ + method: 'POST', + url: '/api/teams', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: teamCreate, + }).then(response => { + teamId = response.body[0].id; + expect(response.status).to.eq(200); + expect(response.body[0]).to.have.property('name', 'cypress'); + expect(response.body[1]).to.have.property('role', 'team-owner'); + }); + }); + }); + + it('Gets a teams by ID.', () => { + cy.request({ + method: 'GET', + url: `/api/teams/${teamId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('id', teamId); + }); + }); + + it('Updates a team.', () => { + cy.fixture('teams').then(data => { + const teamUpdate = data.teamUpdate; + cy.request({ + method: 'POST', + url: `/api/teams/${teamId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: teamUpdate, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('id', teamId); + expect(response.body).to.have.property('name', 'cypressUpdate'); + }); + }); + }); + + it('Get all users that belong to a team.', () => { + cy.request({ + method: 'GET', + url: `/api/teams/${teamId}/users`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body.data[0]).to.have.property('id'); + expect(response.body.data[0]).to.have.property('teamId'); + expect(response.body.data[0]).to.have.property('userId'); + expect(response.body.data[0]).to.have.property('user'); + }); + }); + + it('Get a user belonging to a team.', () => { + cy.request({ + method: 'GET', + url: `/api/teams/${teamId}/users/${Cypress.env('umami_user_id')}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('teamId'); + expect(response.body).to.have.property('userId'); + expect(response.body).to.have.property('role'); + }); + }); + + it('Get all websites belonging to a team.', () => { + cy.request({ + method: 'GET', + url: `/api/teams/${teamId}/websites`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('data'); + }); + }); + + it('Add a user to a team.', () => { + cy.request({ + method: 'POST', + url: `/api/teams/${teamId}/users`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: { + userId, + role: 'team-member', + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('userId', userId); + expect(response.body).to.have.property('role', 'team-member'); + }); + }); + + it(`Update a user's role on a team.`, () => { + cy.request({ + method: 'POST', + url: `/api/teams/${teamId}/users/${userId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: { + role: 'team-view-only', + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('userId', userId); + expect(response.body).to.have.property('role', 'team-view-only'); + }); + }); + + it(`Remove a user from a team.`, () => { + cy.request({ + method: 'DELETE', + url: `/api/teams/${teamId}/users/${userId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + }); + }); + + it('Deletes a team.', () => { + cy.request({ + method: 'DELETE', + url: `/api/teams/${teamId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('ok', true); + }); + }); + + // it('Gets all teams that belong to a user.', () => { + // cy.request({ + // method: 'GET', + // url: `/api/users/${userId}/teams`, + // headers: { + // 'Content-Type': 'application/json', + // Authorization: Cypress.env('authorization'), + // }, + // }).then(response => { + // expect(response.status).to.eq(200); + // expect(response.body).to.have.property('data'); + // }); + // }); + + after(() => { + cy.deleteUser(userId); + }); +}); diff --git a/cypress/e2e/api-user.cy.ts b/cypress/e2e/api-user.cy.ts new file mode 100644 index 00000000..696cd21e --- /dev/null +++ b/cypress/e2e/api-user.cy.ts @@ -0,0 +1,125 @@ +describe('User API tests', () => { + Cypress.session.clearAllSavedSessions(); + + before(() => { + cy.login(Cypress.env('umami_user'), Cypress.env('umami_password')); + }); + + let userId; + + it('Creates a user.', () => { + cy.fixture('users').then(data => { + const userCreate = data.userCreate; + cy.request({ + method: 'POST', + url: '/api/users', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: userCreate, + }).then(response => { + userId = response.body.id; + expect(response.status).to.eq(200); + expect(response.body).to.have.property('username', 'cypress1'); + expect(response.body).to.have.property('role', 'user'); + }); + }); + }); + + it('Returns all users. Admin access is required.', () => { + cy.request({ + method: 'GET', + url: '/api/admin/users', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body.data[0]).to.have.property('id'); + expect(response.body.data[0]).to.have.property('username'); + expect(response.body.data[0]).to.have.property('password'); + expect(response.body.data[0]).to.have.property('role'); + }); + }); + + it('Updates a user.', () => { + cy.fixture('users').then(data => { + const userUpdate = data.userUpdate; + cy.request({ + method: 'POST', + url: `/api/users/${userId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: userUpdate, + }).then(response => { + userId = response.body.id; + expect(response.status).to.eq(200); + expect(response.body).to.have.property('id', userId); + expect(response.body).to.have.property('username', 'cypress1'); + expect(response.body).to.have.property('role', 'view-only'); + }); + }); + }); + + it('Gets a user by ID.', () => { + cy.request({ + method: 'GET', + url: `/api/users/${userId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('id', userId); + expect(response.body).to.have.property('username', 'cypress1'); + expect(response.body).to.have.property('role', 'view-only'); + }); + }); + + it('Deletes a user.', () => { + cy.request({ + method: 'DELETE', + url: `/api/users/${userId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('ok', true); + }); + }); + + it('Gets all websites that belong to a user.', () => { + cy.request({ + method: 'GET', + url: `/api/users/${userId}/websites`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('data'); + }); + }); + + it('Gets all teams that belong to a user.', () => { + cy.request({ + method: 'GET', + url: `/api/users/${userId}/teams`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('data'); + }); + }); +}); diff --git a/cypress/e2e/api-website.cy.ts b/cypress/e2e/api-website.cy.ts new file mode 100644 index 00000000..3fba48d3 --- /dev/null +++ b/cypress/e2e/api-website.cy.ts @@ -0,0 +1,150 @@ +describe('Website API tests', () => { + Cypress.session.clearAllSavedSessions(); + + let websiteId; + let teamId; + + before(() => { + cy.login(Cypress.env('umami_user'), Cypress.env('umami_password')); + cy.fixture('teams').then(data => { + const teamCreate = data.teamCreate; + cy.request({ + method: 'POST', + url: '/api/teams', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: teamCreate, + }).then(response => { + teamId = response.body[0].id; + expect(response.status).to.eq(200); + expect(response.body[0]).to.have.property('name', 'cypress'); + expect(response.body[1]).to.have.property('role', 'team-owner'); + }); + }); + }); + + it('Creates a website for user.', () => { + cy.fixture('websites').then(data => { + const websiteCreate = data.websiteCreate; + cy.request({ + method: 'POST', + url: '/api/websites', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: websiteCreate, + }).then(response => { + websiteId = response.body.id; + expect(response.status).to.eq(200); + expect(response.body).to.have.property('name', 'Cypress Website'); + expect(response.body).to.have.property('domain', 'cypress.com'); + }); + }); + }); + + it('Creates a website for team.', () => { + cy.request({ + method: 'POST', + url: '/api/websites', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: { + name: 'Team Website', + domain: 'teamwebsite.com', + teamId: teamId, + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('name', 'Team Website'); + expect(response.body).to.have.property('domain', 'teamwebsite.com'); + }); + }); + + it('Returns all tracked websites.', () => { + cy.request({ + method: 'GET', + url: '/api/websites', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body.data[0]).to.have.property('id'); + expect(response.body.data[0]).to.have.property('name'); + expect(response.body.data[0]).to.have.property('domain'); + }); + }); + + it('Gets a website by ID.', () => { + cy.request({ + method: 'GET', + url: `/api/websites/${websiteId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('name', 'Cypress Website'); + expect(response.body).to.have.property('domain', 'cypress.com'); + }); + }); + + it('Updates a website.', () => { + cy.fixture('websites').then(data => { + const websiteUpdate = data.websiteUpdate; + cy.request({ + method: 'POST', + url: `/api/websites/${websiteId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: websiteUpdate, + }).then(response => { + websiteId = response.body.id; + expect(response.status).to.eq(200); + expect(response.body).to.have.property('name', 'Cypress Website Updated'); + expect(response.body).to.have.property('domain', 'cypressupdated.com'); + }); + }); + }); + + it('Resets a website by removing all data related to the website.', () => { + cy.request({ + method: 'POST', + url: `/api/websites/${websiteId}/reset`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('ok', true); + }); + }); + + it('Deletes a website.', () => { + cy.request({ + method: 'DELETE', + url: `/api/websites/${websiteId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property('ok', true); + }); + }); + + after(() => { + cy.deleteTeam(teamId); + }); +}); diff --git a/cypress/e2e/api.cy.ts b/cypress/e2e/api.cy.ts deleted file mode 100644 index e69b5dff..00000000 --- a/cypress/e2e/api.cy.ts +++ /dev/null @@ -1,29 +0,0 @@ -describe('Website tests', () => { - Cypress.session.clearAllSavedSessions(); - - beforeEach(() => { - cy.login(Cypress.env('umami_user'), Cypress.env('umami_password')); - }); - - //let userId; - - it('creates a user.', () => { - cy.fixture('users').then(data => { - const userPost = data.userPost; - cy.request({ - method: 'POST', - url: '/api/users', - headers: { - 'Content-Type': 'application/json', - Authorization: Cypress.env('authorization'), - }, - body: userPost, - }).then(response => { - //userId = response.body.id; - expect(response.status).to.eq(200); - expect(response.body).to.have.property('username', 'cypress1'); - expect(response.body).to.have.property('role', 'User'); - }); - }); - }); -}); diff --git a/cypress/e2e/user.cy.ts b/cypress/e2e/user.cy.ts index 9f432f16..24df3053 100644 --- a/cypress/e2e/user.cy.ts +++ b/cypress/e2e/user.cy.ts @@ -1,4 +1,4 @@ -describe('Website tests', () => { +describe('User tests', () => { Cypress.session.clearAllSavedSessions(); beforeEach(() => { @@ -51,7 +51,7 @@ describe('Website tests', () => { cy.url().should('eq', Cypress.config().baseUrl + '/dashboard'); }); - it('Delete a website', () => { + it('Delete a user', () => { // delete user cy.get('table tbody tr') .contains('td', /Test-user/i) diff --git a/cypress/fixtures/teams.json b/cypress/fixtures/teams.json new file mode 100644 index 00000000..4c001e65 --- /dev/null +++ b/cypress/fixtures/teams.json @@ -0,0 +1,8 @@ +{ + "teamCreate": { + "name": "cypress" + }, + "teamUpdate": { + "name": "cypressUpdate" + } +} diff --git a/cypress/fixtures/users.json b/cypress/fixtures/users.json index 420a71c3..235f0d63 100644 --- a/cypress/fixtures/users.json +++ b/cypress/fixtures/users.json @@ -1,17 +1,11 @@ { - "userGet": { - "name": "cypress", - "email": "password", - "role": "User" - }, - "userPost": { + "userCreate": { "username": "cypress1", "password": "password", - "role": "User" + "role": "user" }, - "userDelete": { - "name": "Charlie", - "email": "charlie@example.com", - "age": 35 + "userUpdate": { + "username": "cypress1", + "role": "view-only" } } diff --git a/cypress/fixtures/websites.json b/cypress/fixtures/websites.json new file mode 100644 index 00000000..0b817889 --- /dev/null +++ b/cypress/fixtures/websites.json @@ -0,0 +1,10 @@ +{ + "websiteCreate": { + "name": "Cypress Website", + "domain": "cypress.com" + }, + "websiteUpdate": { + "name": "Cypress Website Updated", + "domain": "cypressupdated.com" + } +} diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index a300b969..a95035ba 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -61,3 +61,63 @@ Cypress.Commands.add('deleteWebsite', (websiteId: string) => { expect(response.status).to.eq(200); }); }); + +Cypress.Commands.add('addUser', (username: string, password: string, role: string) => { + cy.request({ + method: 'POST', + url: '/api/users', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: { + username: username, + password: password, + role: role, + }, + }).then(response => { + expect(response.status).to.eq(200); + }); +}); + +Cypress.Commands.add('deleteUser', (userId: string) => { + cy.request({ + method: 'DELETE', + url: `/api/users/${userId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + }); +}); + +Cypress.Commands.add('addTeam', (name: string) => { + cy.request({ + method: 'POST', + url: '/api/teams', + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + body: { + name: name, + }, + }).then(response => { + expect(response.status).to.eq(200); + }); +}); + +Cypress.Commands.add('deleteTeam', (teamId: string) => { + cy.request({ + method: 'DELETE', + url: `/api/teams/${teamId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: Cypress.env('authorization'), + }, + }).then(response => { + expect(response.status).to.eq(200); + }); +}); diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index e89b24dd..b6302690 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -24,9 +24,33 @@ declare namespace Cypress { */ addWebsite(name: string, domain: string): Chainable>; /** - * Custom command to create a website + * Custom command to delete a website * @example cy.deleteWebsite('02d89813-7a72-41e1-87f0-8d668f85008b') */ deleteWebsite(websiteId: string): Chainable>; + /** + * Custom command to create a website + * @example cy.deleteWebsite('02d89813-7a72-41e1-87f0-8d668f85008b') + */ + /** + * Custom command to create a user + * @example cy.addUser('cypress', 'password', 'User') + */ + addUser(username: string, password: string, role: string): Chainable>; + /** + * Custom command to delete a user + * @example cy.deleteUser('02d89813-7a72-41e1-87f0-8d668f85008b') + */ + deleteUser(userId: string): Chainable>; + /** + * Custom command to create a team + * @example cy.addTeam('cypressTeam') + */ + addTeam(name: string): Chainable>; + /** + * Custom command to create a website + * @example cy.deleteTeam('02d89813-7a72-41e1-87f0-8d668f85008b') + */ + deleteTeam(teamId: string): Chainable>; } } diff --git a/src/app/api/teams/[teamId]/route.ts b/src/app/api/teams/[teamId]/route.ts index f7f4b331..194e7bbb 100644 --- a/src/app/api/teams/[teamId]/route.ts +++ b/src/app/api/teams/[teamId]/route.ts @@ -28,8 +28,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ team export async function POST(request: Request, { params }: { params: Promise<{ teamId: string }> }) { const schema = z.object({ - name: z.string().max(50), - accessCode: z.string().max(50), + name: z.string().max(50).optional(), + accessCode: z.string().max(50).optional(), }); const { auth, body, error } = await parseRequest(request, schema); diff --git a/src/app/api/websites/[websiteId]/route.ts b/src/app/api/websites/[websiteId]/route.ts index f4ea327b..346e5856 100644 --- a/src/app/api/websites/[websiteId]/route.ts +++ b/src/app/api/websites/[websiteId]/route.ts @@ -33,7 +33,7 @@ export async function POST( const schema = z.object({ name: z.string(), domain: z.string(), - shareId: z.string().regex(SHARE_ID_REGEX).nullable(), + shareId: z.string().regex(SHARE_ID_REGEX).nullable().optional(), }); const { auth, body, error } = await parseRequest(request, schema);