From 7e726a9916aff060458650218111d6578d1e108d Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Mon, 25 Nov 2024 21:11:21 +0100 Subject: [PATCH 01/34] Add test for programming exercise submissions through git using token based https --- .../code-button/code-button.component.html | 1 + .../ssh-user-settings.component.html | 4 +- .../ProgrammingExerciseParticipation.spec.ts | 65 ++++++++++++++++--- .../ProgrammingExerciseOverviewPage.ts | 28 +++++++- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/src/main/webapp/app/shared/components/code-button/code-button.component.html b/src/main/webapp/app/shared/components/code-button/code-button.component.html index 80972a802a38..762337dc5157 100644 --- a/src/main/webapp/app/shared/components/code-button/code-button.component.html +++ b/src/main/webapp/app/shared/components/code-button/code-button.component.html @@ -95,6 +95,7 @@
{{ cloneHeadline | artemisTranslate }}
[disabled]="!copyEnabled" [class.btn-success]="wasCopied" class="btn btn-primary btn-sm me-2" + data-testid="copyRepoUrlButton" type="button" style="min-width: 100px" jhiTranslate="{{ wasCopied ? 'artemisApp.exerciseActions.copiedUrl' : 'artemisApp.exerciseActions.copyUrl' }}" diff --git a/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html b/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html index 7372a07ac241..bf00cf226af8 100644 --- a/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html +++ b/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html @@ -22,6 +22,7 @@

@@ -107,7 +108,7 @@

- +
@@ -127,6 +128,7 @@

[icon]="faSave" [title]="'artemisApp.userSettings.sshSettingsPage.saveSshKey'" (onClick)="saveSshKey()" + data-test-id="saveSshKeyButton" />
diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index cd28fe48b5b0..13f8a93bb5e6 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -7,7 +7,7 @@ import javaPartiallySuccessfulSubmission from '../../../fixtures/exercise/progra import pythonAllSuccessful from '../../../fixtures/exercise/programming/python/all_successful/submission.json'; import { ExerciseCommit, ExerciseMode, ProgrammingLanguage } from '../../../support/constants'; import { test } from '../../../support/fixtures'; -import { Page, expect } from '@playwright/test'; +import { BrowserContext, Page, expect } from '@playwright/test'; import { gitClient } from '../../../support/pageobjects/exercises/programming/GitClient'; import * as fs from 'fs/promises'; import { SimpleGit } from 'simple-git'; @@ -17,7 +17,7 @@ import { ProgrammingExerciseSubmission } from '../../../support/pageobjects/exer import cAllSuccessful from '../../../fixtures/exercise/programming/c/all_successful/submission.json'; import { UserCredentials, admin, instructor, studentFour, studentOne, studentTwo, tutor } from '../../../support/users'; import { Team } from 'app/entities/team.model'; -import { ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; +import { GitCloneMethod, ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; import { Participation } from 'app/entities/participation/participation.model'; test.describe('Programming exercise participation', { tag: '@sequential' }, () => { @@ -88,6 +88,34 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = } } + test.describe('Programming exercise participation using secure git', () => { + let exercise: ProgrammingExercise; + + test.beforeEach('Setup programming exercise', async ({ login, exerciseAPIRequests }) => { + await login(admin); + exercise = await exerciseAPIRequests.createProgrammingExercise({ course, programmingLanguage: ProgrammingLanguage.JAVA }); + }); + + // test('Makes a submission using ssh git', async ({ page, programmingExerciseOverview }) => { + // await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); + // await makeGitExerciseSubmission(page, programmingExerciseOverview, course, exercise, studentOne, javaAllSuccessfulSubmission, 'Solution', GitCloneMethod.ssh); + // }); + + test('Makes a submission using git through HTTPS using token', async ({ programmingExerciseOverview, page }) => { + await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); + await makeGitExerciseSubmission( + page, + programmingExerciseOverview, + course, + exercise, + studentOne, + javaAllSuccessfulSubmission, + 'Solution', + GitCloneMethod.httpsWithToken, + ); + }); + }); + test.describe('Programming exercise team participation', () => { let exercise: ProgrammingExercise; let participation: Participation; @@ -221,9 +249,9 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = }); }); - test.afterEach('Delete course', async ({ courseManagementAPIRequests }) => { - await courseManagementAPIRequests.deleteCourse(course, admin); - }); + // test.afterEach('Delete course', async ({ courseManagementAPIRequests }) => { + // await courseManagementAPIRequests.deleteCourse(course, admin); + // }); }); async function makeGitExerciseSubmission( @@ -234,13 +262,24 @@ async function makeGitExerciseSubmission( student: UserCredentials, submission: any, commitMessage: string, + cloneMethod: GitCloneMethod = GitCloneMethod.https, ) { - let repoUrl = await programmingExerciseOverview.getRepoUrl(); + await programmingExerciseOverview.openCloneMenu(cloneMethod); + if (cloneMethod == GitCloneMethod.ssh) { + await setupSSHCredentials(page.context()); + await page.reload(); + await programmingExerciseOverview.openCloneMenu(cloneMethod); + } + let repoUrl = await programmingExerciseOverview.copyCloneUrl(); + console.log('Cloning repository:', repoUrl); + console.log('repo url length:', repoUrl.length); if (process.env.CI === 'true') { repoUrl = repoUrl.replace('localhost', 'artemis-app'); } - repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); - repoUrl = repoUrl.replace(`:**********`, ``); + if (cloneMethod == GitCloneMethod.https) { + repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); + repoUrl = repoUrl.replace(`:**********`, ``); + } const urlParts = repoUrl.split('/'); const repoName = urlParts[urlParts.length - 1]; const exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName); @@ -251,6 +290,16 @@ async function makeGitExerciseSubmission( await expect(resultScore.getByText(submission.expectedResult)).toBeVisible(); } +async function setupSSHCredentials(context: BrowserContext) { + const page = await context.newPage(); + // TODO: Implement SSH key generation + const sshKey = ''; + await page.goto('user-settings/ssh'); + await page.getByTestId('addNewSshKeyButton').click(); + await page.getByTestId('sshKeyField').fill(sshKey!); + await page.getByTestId('saveSshKeyButton').click(); +} + /** * Helper function to make a submission to a git repository. * @param exerciseRepo - The git repository to which the submission should be made. diff --git a/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts b/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts index cc7243718a96..d4b3fef78753 100644 --- a/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts +++ b/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts @@ -33,14 +33,34 @@ export class ProgrammingExerciseOverviewPage { await this.courseOverview.openRunningProgrammingExercise(exerciseId); } - async getRepoUrl() { + async openCloneMenu(cloneMethod: GitCloneMethod) { + const gitCloneMethodSelector = { + [GitCloneMethod.https]: '#useHTTPSButton', + [GitCloneMethod.httpsWithToken]: '#useHTTPSWithTokenButton', + [GitCloneMethod.ssh]: '#useSSHButton', + }; + const codeButtonLocator = this.getCodeButton(); await Commands.reloadUntilFound(this.page, codeButtonLocator, 4000, 20000); await codeButtonLocator.click(); await this.page.locator('.popover-body').waitFor({ state: 'visible' }); + await this.page.locator('.https-or-ssh-button').click(); + await this.page.locator(gitCloneMethodSelector[cloneMethod]).click(); + return await this.page.locator('.clone-url').innerText(); + } + + async getCloneUrl() { return await this.page.locator('.clone-url').innerText(); } + async copyCloneUrl() { + await this.page.context().grantPermissions(['clipboard-read', 'clipboard-write']); + await this.page.getByTestId('copyRepoUrlButton').click(); + return await this.page.evaluate(async () => { + return await navigator.clipboard.readText(); + }); + } + getCodeButton() { return this.page.locator('.code-button'); } @@ -49,3 +69,9 @@ export class ProgrammingExerciseOverviewPage { return this.page.locator('#course-exercise-details'); } } + +export enum GitCloneMethod { + https, + httpsWithToken, + ssh, +} From a2caef08ee029d0ec9d34c7202f457461b70f013 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 26 Nov 2024 12:44:25 +0100 Subject: [PATCH 02/34] Revert "Add test for programming exercise submissions through git using token based https" This reverts commit 7e726a9916aff060458650218111d6578d1e108d. --- .../code-button/code-button.component.html | 1 - .../ssh-user-settings.component.html | 4 +- .../ProgrammingExerciseParticipation.spec.ts | 65 +++---------------- .../ProgrammingExerciseOverviewPage.ts | 28 +------- 4 files changed, 10 insertions(+), 88 deletions(-) diff --git a/src/main/webapp/app/shared/components/code-button/code-button.component.html b/src/main/webapp/app/shared/components/code-button/code-button.component.html index 762337dc5157..80972a802a38 100644 --- a/src/main/webapp/app/shared/components/code-button/code-button.component.html +++ b/src/main/webapp/app/shared/components/code-button/code-button.component.html @@ -95,7 +95,6 @@
{{ cloneHeadline | artemisTranslate }}
[disabled]="!copyEnabled" [class.btn-success]="wasCopied" class="btn btn-primary btn-sm me-2" - data-testid="copyRepoUrlButton" type="button" style="min-width: 100px" jhiTranslate="{{ wasCopied ? 'artemisApp.exerciseActions.copiedUrl' : 'artemisApp.exerciseActions.copyUrl' }}" diff --git a/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html b/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html index bf00cf226af8..7372a07ac241 100644 --- a/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html +++ b/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html @@ -22,7 +22,6 @@

@@ -108,7 +107,7 @@

- +
@@ -128,7 +127,6 @@

[icon]="faSave" [title]="'artemisApp.userSettings.sshSettingsPage.saveSshKey'" (onClick)="saveSshKey()" - data-test-id="saveSshKeyButton" />
diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 13f8a93bb5e6..cd28fe48b5b0 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -7,7 +7,7 @@ import javaPartiallySuccessfulSubmission from '../../../fixtures/exercise/progra import pythonAllSuccessful from '../../../fixtures/exercise/programming/python/all_successful/submission.json'; import { ExerciseCommit, ExerciseMode, ProgrammingLanguage } from '../../../support/constants'; import { test } from '../../../support/fixtures'; -import { BrowserContext, Page, expect } from '@playwright/test'; +import { Page, expect } from '@playwright/test'; import { gitClient } from '../../../support/pageobjects/exercises/programming/GitClient'; import * as fs from 'fs/promises'; import { SimpleGit } from 'simple-git'; @@ -17,7 +17,7 @@ import { ProgrammingExerciseSubmission } from '../../../support/pageobjects/exer import cAllSuccessful from '../../../fixtures/exercise/programming/c/all_successful/submission.json'; import { UserCredentials, admin, instructor, studentFour, studentOne, studentTwo, tutor } from '../../../support/users'; import { Team } from 'app/entities/team.model'; -import { GitCloneMethod, ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; +import { ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; import { Participation } from 'app/entities/participation/participation.model'; test.describe('Programming exercise participation', { tag: '@sequential' }, () => { @@ -88,34 +88,6 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = } } - test.describe('Programming exercise participation using secure git', () => { - let exercise: ProgrammingExercise; - - test.beforeEach('Setup programming exercise', async ({ login, exerciseAPIRequests }) => { - await login(admin); - exercise = await exerciseAPIRequests.createProgrammingExercise({ course, programmingLanguage: ProgrammingLanguage.JAVA }); - }); - - // test('Makes a submission using ssh git', async ({ page, programmingExerciseOverview }) => { - // await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); - // await makeGitExerciseSubmission(page, programmingExerciseOverview, course, exercise, studentOne, javaAllSuccessfulSubmission, 'Solution', GitCloneMethod.ssh); - // }); - - test('Makes a submission using git through HTTPS using token', async ({ programmingExerciseOverview, page }) => { - await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); - await makeGitExerciseSubmission( - page, - programmingExerciseOverview, - course, - exercise, - studentOne, - javaAllSuccessfulSubmission, - 'Solution', - GitCloneMethod.httpsWithToken, - ); - }); - }); - test.describe('Programming exercise team participation', () => { let exercise: ProgrammingExercise; let participation: Participation; @@ -249,9 +221,9 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = }); }); - // test.afterEach('Delete course', async ({ courseManagementAPIRequests }) => { - // await courseManagementAPIRequests.deleteCourse(course, admin); - // }); + test.afterEach('Delete course', async ({ courseManagementAPIRequests }) => { + await courseManagementAPIRequests.deleteCourse(course, admin); + }); }); async function makeGitExerciseSubmission( @@ -262,24 +234,13 @@ async function makeGitExerciseSubmission( student: UserCredentials, submission: any, commitMessage: string, - cloneMethod: GitCloneMethod = GitCloneMethod.https, ) { - await programmingExerciseOverview.openCloneMenu(cloneMethod); - if (cloneMethod == GitCloneMethod.ssh) { - await setupSSHCredentials(page.context()); - await page.reload(); - await programmingExerciseOverview.openCloneMenu(cloneMethod); - } - let repoUrl = await programmingExerciseOverview.copyCloneUrl(); - console.log('Cloning repository:', repoUrl); - console.log('repo url length:', repoUrl.length); + let repoUrl = await programmingExerciseOverview.getRepoUrl(); if (process.env.CI === 'true') { repoUrl = repoUrl.replace('localhost', 'artemis-app'); } - if (cloneMethod == GitCloneMethod.https) { - repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); - repoUrl = repoUrl.replace(`:**********`, ``); - } + repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); + repoUrl = repoUrl.replace(`:**********`, ``); const urlParts = repoUrl.split('/'); const repoName = urlParts[urlParts.length - 1]; const exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName); @@ -290,16 +251,6 @@ async function makeGitExerciseSubmission( await expect(resultScore.getByText(submission.expectedResult)).toBeVisible(); } -async function setupSSHCredentials(context: BrowserContext) { - const page = await context.newPage(); - // TODO: Implement SSH key generation - const sshKey = ''; - await page.goto('user-settings/ssh'); - await page.getByTestId('addNewSshKeyButton').click(); - await page.getByTestId('sshKeyField').fill(sshKey!); - await page.getByTestId('saveSshKeyButton').click(); -} - /** * Helper function to make a submission to a git repository. * @param exerciseRepo - The git repository to which the submission should be made. diff --git a/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts b/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts index d4b3fef78753..cc7243718a96 100644 --- a/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts +++ b/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts @@ -33,34 +33,14 @@ export class ProgrammingExerciseOverviewPage { await this.courseOverview.openRunningProgrammingExercise(exerciseId); } - async openCloneMenu(cloneMethod: GitCloneMethod) { - const gitCloneMethodSelector = { - [GitCloneMethod.https]: '#useHTTPSButton', - [GitCloneMethod.httpsWithToken]: '#useHTTPSWithTokenButton', - [GitCloneMethod.ssh]: '#useSSHButton', - }; - + async getRepoUrl() { const codeButtonLocator = this.getCodeButton(); await Commands.reloadUntilFound(this.page, codeButtonLocator, 4000, 20000); await codeButtonLocator.click(); await this.page.locator('.popover-body').waitFor({ state: 'visible' }); - await this.page.locator('.https-or-ssh-button').click(); - await this.page.locator(gitCloneMethodSelector[cloneMethod]).click(); - return await this.page.locator('.clone-url').innerText(); - } - - async getCloneUrl() { return await this.page.locator('.clone-url').innerText(); } - async copyCloneUrl() { - await this.page.context().grantPermissions(['clipboard-read', 'clipboard-write']); - await this.page.getByTestId('copyRepoUrlButton').click(); - return await this.page.evaluate(async () => { - return await navigator.clipboard.readText(); - }); - } - getCodeButton() { return this.page.locator('.code-button'); } @@ -69,9 +49,3 @@ export class ProgrammingExerciseOverviewPage { return this.page.locator('#course-exercise-details'); } } - -export enum GitCloneMethod { - https, - httpsWithToken, - ssh, -} From 83ee37217f1bf554dea88f14c244ca999d0d1118 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 26 Nov 2024 12:58:00 +0100 Subject: [PATCH 03/34] Add test for programming exercise submissions through git using token based https --- .../code-button/code-button.component.html | 1 + .../ProgrammingExerciseParticipation.spec.ts | 37 ++++++++++++++++--- .../ProgrammingExerciseOverviewPage.ts | 28 +++++++++++++- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/main/webapp/app/shared/components/code-button/code-button.component.html b/src/main/webapp/app/shared/components/code-button/code-button.component.html index 80972a802a38..762337dc5157 100644 --- a/src/main/webapp/app/shared/components/code-button/code-button.component.html +++ b/src/main/webapp/app/shared/components/code-button/code-button.component.html @@ -95,6 +95,7 @@
{{ cloneHeadline | artemisTranslate }}
[disabled]="!copyEnabled" [class.btn-success]="wasCopied" class="btn btn-primary btn-sm me-2" + data-testid="copyRepoUrlButton" type="button" style="min-width: 100px" jhiTranslate="{{ wasCopied ? 'artemisApp.exerciseActions.copiedUrl' : 'artemisApp.exerciseActions.copyUrl' }}" diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index cd28fe48b5b0..8bc5240027b0 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -17,7 +17,7 @@ import { ProgrammingExerciseSubmission } from '../../../support/pageobjects/exer import cAllSuccessful from '../../../fixtures/exercise/programming/c/all_successful/submission.json'; import { UserCredentials, admin, instructor, studentFour, studentOne, studentTwo, tutor } from '../../../support/users'; import { Team } from 'app/entities/team.model'; -import { ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; +import { GitCloneMethod, ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; import { Participation } from 'app/entities/participation/participation.model'; test.describe('Programming exercise participation', { tag: '@sequential' }, () => { @@ -80,7 +80,7 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = }); }); - test('Makes a submission using git', async ({ page, programmingExerciseOverview }) => { + test('Makes a git submission through HTTPS', async ({ page, programmingExerciseOverview }) => { await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); await makeGitExerciseSubmission(page, programmingExerciseOverview, course, exercise, studentOne, submission, commitMessage); }); @@ -88,6 +88,29 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = } } + test.describe('Programming exercise participation using secure git', () => { + let exercise: ProgrammingExercise; + + test.beforeEach('Setup programming exercise', async ({ login, exerciseAPIRequests }) => { + await login(admin); + exercise = await exerciseAPIRequests.createProgrammingExercise({ course, programmingLanguage: ProgrammingLanguage.JAVA }); + }); + + test('Makes a git submission through HTTPS using token', async ({ programmingExerciseOverview, page }) => { + await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); + await makeGitExerciseSubmission( + page, + programmingExerciseOverview, + course, + exercise, + studentOne, + javaAllSuccessfulSubmission, + 'Solution', + GitCloneMethod.httpsWithToken, + ); + }); + }); + test.describe('Programming exercise team participation', () => { let exercise: ProgrammingExercise; let participation: Participation; @@ -234,13 +257,17 @@ async function makeGitExerciseSubmission( student: UserCredentials, submission: any, commitMessage: string, + cloneMethod: GitCloneMethod = GitCloneMethod.https, ) { - let repoUrl = await programmingExerciseOverview.getRepoUrl(); + await programmingExerciseOverview.openCloneMenu(cloneMethod); + let repoUrl = await programmingExerciseOverview.copyCloneUrl(); if (process.env.CI === 'true') { repoUrl = repoUrl.replace('localhost', 'artemis-app'); } - repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); - repoUrl = repoUrl.replace(`:**********`, ``); + if (cloneMethod == GitCloneMethod.https) { + repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); + repoUrl = repoUrl.replace(`:**********`, ``); + } const urlParts = repoUrl.split('/'); const repoName = urlParts[urlParts.length - 1]; const exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName); diff --git a/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts b/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts index cc7243718a96..d4b3fef78753 100644 --- a/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts +++ b/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts @@ -33,14 +33,34 @@ export class ProgrammingExerciseOverviewPage { await this.courseOverview.openRunningProgrammingExercise(exerciseId); } - async getRepoUrl() { + async openCloneMenu(cloneMethod: GitCloneMethod) { + const gitCloneMethodSelector = { + [GitCloneMethod.https]: '#useHTTPSButton', + [GitCloneMethod.httpsWithToken]: '#useHTTPSWithTokenButton', + [GitCloneMethod.ssh]: '#useSSHButton', + }; + const codeButtonLocator = this.getCodeButton(); await Commands.reloadUntilFound(this.page, codeButtonLocator, 4000, 20000); await codeButtonLocator.click(); await this.page.locator('.popover-body').waitFor({ state: 'visible' }); + await this.page.locator('.https-or-ssh-button').click(); + await this.page.locator(gitCloneMethodSelector[cloneMethod]).click(); + return await this.page.locator('.clone-url').innerText(); + } + + async getCloneUrl() { return await this.page.locator('.clone-url').innerText(); } + async copyCloneUrl() { + await this.page.context().grantPermissions(['clipboard-read', 'clipboard-write']); + await this.page.getByTestId('copyRepoUrlButton').click(); + return await this.page.evaluate(async () => { + return await navigator.clipboard.readText(); + }); + } + getCodeButton() { return this.page.locator('.code-button'); } @@ -49,3 +69,9 @@ export class ProgrammingExerciseOverviewPage { return this.page.locator('#course-exercise-details'); } } + +export enum GitCloneMethod { + https, + httpsWithToken, + ssh, +} From 52b0def8d617c949e953718445f67ebfcbfe3103 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 26 Nov 2024 15:10:48 +0100 Subject: [PATCH 04/34] Don't modify url in token based cloning, log repo url --- .../programming/ProgrammingExerciseParticipation.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 8bc5240027b0..a6603a50821f 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -261,13 +261,14 @@ async function makeGitExerciseSubmission( ) { await programmingExerciseOverview.openCloneMenu(cloneMethod); let repoUrl = await programmingExerciseOverview.copyCloneUrl(); - if (process.env.CI === 'true') { + if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.https) { repoUrl = repoUrl.replace('localhost', 'artemis-app'); } if (cloneMethod == GitCloneMethod.https) { repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); repoUrl = repoUrl.replace(`:**********`, ``); } + console.log(`Cloning repository from ${repoUrl}`); const urlParts = repoUrl.split('/'); const repoName = urlParts[urlParts.length - 1]; const exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName); From 93a001525ccc567e5ba22555a5f2dc56f223b9c1 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sun, 1 Dec 2024 00:13:24 +0100 Subject: [PATCH 05/34] Add test that uses SSH for participating in programming exercise --- docker/artemis/config/playwright-local.env | 4 ++- docker/playwright.yml | 4 +++ .../ssh-user-settings.component.html | 4 ++- .../ProgrammingExerciseParticipation.spec.ts | 36 +++++++++++++++++-- src/test/playwright/support/fixtures.ts | 5 +++ .../ProgrammingExerciseOverviewPage.ts | 7 ++-- .../requests/AccountManagementAPIRequests.ts | 14 ++++++++ 7 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 src/test/playwright/support/requests/AccountManagementAPIRequests.ts diff --git a/docker/artemis/config/playwright-local.env b/docker/artemis/config/playwright-local.env index 6b7805deacca..ecc6eefc0387 100644 --- a/docker/artemis/config/playwright-local.env +++ b/docker/artemis/config/playwright-local.env @@ -1,5 +1,5 @@ # ---------------------------------------------------------------------------------------------------------------------- -# Artemis configuration overrides for the Playwright E2E Postgres setups +# Artemis configuration overrides for the Playwright E2E Local CI/VC setups # ---------------------------------------------------------------------------------------------------------------------- SPRING_PROFILES_ACTIVE="artemis,scheduling,localvc,localci,buildagent,core,prod,docker" @@ -12,3 +12,5 @@ ARTEMIS_CONTINUOUSINTEGRATION_ARTEMISAUTHENTICATIONTOKENVALUE='demo' ARTEMIS_CONTINUOUSINTEGRATION_DOCKERCONNECTIONURI='unix:///var/run/docker.sock' ARTEMIS_GIT_NAME='artemis' ARTEMIS_GIT_EMAIL='artemis@example.com' +ARTEMIS_VERSIONCONTROL_SSHHOSTKEYPATH='/app/artemis/src/test/playwright/ssh-keys' +ARTEMIS_VERSIONCONTROL_SSHPORT='7921' diff --git a/docker/playwright.yml b/docker/playwright.yml index a27ba2f9d589..85a0cfbf0872 100644 --- a/docker/playwright.yml +++ b/docker/playwright.yml @@ -26,7 +26,11 @@ services: FAST_TEST_TIMEOUT_SECONDS: '${bamboo_fast_test_timeout_seconds}' command: > sh -c ' + ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -q -N ""; + ssh-keyscan -H -p 7921 artemis-app >> ~/.ssh/known_hosts; cd /app/artemis/src/test/playwright && + mkdir -p ssh-keys; + cp ~/.ssh/id_rsa.pub ./ssh-keys/id_rsa.pub; chmod 777 /root && npm ci && npm run playwright:setup && diff --git a/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html b/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html index 7372a07ac241..b8ecf7eefa61 100644 --- a/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html +++ b/src/main/webapp/app/shared/user-settings/ssh-settings/ssh-user-settings.component.html @@ -22,6 +22,7 @@

@@ -107,7 +108,7 @@

- +
@@ -127,6 +128,7 @@

[icon]="faSave" [title]="'artemisApp.userSettings.sshSettingsPage.saveSshKey'" (onClick)="saveSshKey()" + data-testid="saveSshKeyButton" />
diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index a6603a50821f..0d9dfa7e450c 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -5,11 +5,12 @@ import javaAllSuccessfulSubmission from '../../../fixtures/exercise/programming/ import javaBuildErrorSubmission from '../../../fixtures/exercise/programming/java/build_error/submission.json'; import javaPartiallySuccessfulSubmission from '../../../fixtures/exercise/programming/java/partially_successful/submission.json'; import pythonAllSuccessful from '../../../fixtures/exercise/programming/python/all_successful/submission.json'; -import { ExerciseCommit, ExerciseMode, ProgrammingLanguage } from '../../../support/constants'; +import { BASE_API, ExerciseCommit, ExerciseMode, ProgrammingLanguage } from '../../../support/constants'; import { test } from '../../../support/fixtures'; -import { Page, expect } from '@playwright/test'; +import { BrowserContext, Page, expect } from '@playwright/test'; import { gitClient } from '../../../support/pageobjects/exercises/programming/GitClient'; import * as fs from 'fs/promises'; +import path from 'path'; import { SimpleGit } from 'simple-git'; import { Fixtures } from '../../../fixtures/fixtures'; import { createFileWithContent } from '../../../support/utils'; @@ -109,6 +110,15 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = GitCloneMethod.httpsWithToken, ); }); + + test('Makes a submission using SSH git', async ({ page, programmingExerciseOverview }) => { + await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); + await makeGitExerciseSubmission(page, programmingExerciseOverview, course, exercise, studentOne, javaAllSuccessfulSubmission, 'Solution', GitCloneMethod.ssh); + }); + + test.afterEach('Delete SSH key', async ({ accountManagementAPIRequests }) => { + await accountManagementAPIRequests.deleteSshPublicKey(); + }); }); test.describe('Programming exercise team participation', () => { @@ -260,6 +270,14 @@ async function makeGitExerciseSubmission( cloneMethod: GitCloneMethod = GitCloneMethod.https, ) { await programmingExerciseOverview.openCloneMenu(cloneMethod); + if (cloneMethod == GitCloneMethod.ssh) { + await expect(programmingExerciseOverview.getCloneUrlButton()).toBeDisabled(); + const sshKeyNotFoundAlert = page.locator('.alert', { hasText: 'To use ssh, you need to add an ssh key to your account' }); + await expect(sshKeyNotFoundAlert).toBeVisible(); + await setupSSHCredentials(page.context()); + await page.reload(); + await programmingExerciseOverview.openCloneMenu(cloneMethod); + } let repoUrl = await programmingExerciseOverview.copyCloneUrl(); if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.https) { repoUrl = repoUrl.replace('localhost', 'artemis-app'); @@ -272,6 +290,7 @@ async function makeGitExerciseSubmission( const urlParts = repoUrl.split('/'); const repoName = urlParts[urlParts.length - 1]; const exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName); + console.log(`Cloned repository successfully. Pushing files...`); await pushGitSubmissionFiles(exerciseRepo, repoName, student, submission, commitMessage); await fs.rmdir(`./test-exercise-repos/${repoName}`, { recursive: true }); await page.goto(`courses/${course.id}/exercises/${exercise.id!}`); @@ -279,6 +298,19 @@ async function makeGitExerciseSubmission( await expect(resultScore.getByText(submission.expectedResult)).toBeVisible(); } +async function setupSSHCredentials(context: BrowserContext) { + const page = await context.newPage(); + const projectRoot = process.cwd(); + const sshKeyPath = path.join(projectRoot, 'ssh-keys', 'id_rsa.pub'); + const sshKey = await fs.readFile(sshKeyPath, 'utf8'); + await page.goto('user-settings/ssh'); + await page.getByTestId('addNewSshKeyButton').click(); + await page.getByTestId('sshKeyField').fill(sshKey!); + const responsePromise = page.waitForResponse(`${BASE_API}/account/ssh-public-key`); + await page.getByTestId('saveSshKeyButton').click(); + await responsePromise; +} + /** * Helper function to make a submission to a git repository. * @param exerciseRepo - The git repository to which the submission should be made. diff --git a/src/test/playwright/support/fixtures.ts b/src/test/playwright/support/fixtures.ts index dbf28d32cc37..9d25ab252f19 100644 --- a/src/test/playwright/support/fixtures.ts +++ b/src/test/playwright/support/fixtures.ts @@ -67,6 +67,7 @@ import { QuizExerciseOverviewPage } from './pageobjects/exercises/quiz/QuizExerc import { QuizExerciseParticipationPage } from './pageobjects/exercises/quiz/QuizExerciseParticipationPage'; import { ModalDialogBox } from './pageobjects/exam/ModalDialogBox'; import { ExamParticipationActions } from './pageobjects/exam/ExamParticipationActions'; +import { AccountManagementAPIRequests } from './requests/AccountManagementAPIRequests'; /* * Define custom types for fixtures @@ -141,6 +142,7 @@ export type ArtemisPageObjects = { }; export type ArtemisRequests = { + accountManagementAPIRequests: AccountManagementAPIRequests; courseManagementAPIRequests: CourseManagementAPIRequests; userManagementAPIRequests: UserManagementAPIRequests; exerciseAPIRequests: ExerciseAPIRequests; @@ -361,6 +363,9 @@ export const test = base.extend { await use(new ExerciseTeamsPage(page)); }, + accountManagementAPIRequests: async ({ page }, use) => { + await use(new AccountManagementAPIRequests(page)); + }, courseManagementAPIRequests: async ({ page }, use) => { await use(new CourseManagementAPIRequests(page)); }, diff --git a/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts b/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts index d4b3fef78753..e0a7b550478a 100644 --- a/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts +++ b/src/test/playwright/support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage.ts @@ -46,7 +46,6 @@ export class ProgrammingExerciseOverviewPage { await this.page.locator('.popover-body').waitFor({ state: 'visible' }); await this.page.locator('.https-or-ssh-button').click(); await this.page.locator(gitCloneMethodSelector[cloneMethod]).click(); - return await this.page.locator('.clone-url').innerText(); } async getCloneUrl() { @@ -55,7 +54,7 @@ export class ProgrammingExerciseOverviewPage { async copyCloneUrl() { await this.page.context().grantPermissions(['clipboard-read', 'clipboard-write']); - await this.page.getByTestId('copyRepoUrlButton').click(); + await this.getCloneUrlButton().click(); return await this.page.evaluate(async () => { return await navigator.clipboard.readText(); }); @@ -68,6 +67,10 @@ export class ProgrammingExerciseOverviewPage { getExerciseDetails() { return this.page.locator('#course-exercise-details'); } + + getCloneUrlButton() { + return this.page.getByTestId('copyRepoUrlButton'); + } } export enum GitCloneMethod { diff --git a/src/test/playwright/support/requests/AccountManagementAPIRequests.ts b/src/test/playwright/support/requests/AccountManagementAPIRequests.ts new file mode 100644 index 000000000000..12bfa5e14a7e --- /dev/null +++ b/src/test/playwright/support/requests/AccountManagementAPIRequests.ts @@ -0,0 +1,14 @@ +import { BASE_API } from '../constants'; +import { Page } from '@playwright/test'; + +export class AccountManagementAPIRequests { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async deleteSshPublicKey() { + return await this.page.request.delete(`${BASE_API}/account/ssh-public-key`); + } +} From 92fbab51925346893912244a1074209edd73d54f Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Mon, 2 Dec 2024 01:36:08 +0100 Subject: [PATCH 06/34] Modify git clone url to include artemis-app for https with token on CI --- .../programming/ProgrammingExerciseParticipation.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 0d9dfa7e450c..fb9668c5b8a9 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -279,7 +279,7 @@ async function makeGitExerciseSubmission( await programmingExerciseOverview.openCloneMenu(cloneMethod); } let repoUrl = await programmingExerciseOverview.copyCloneUrl(); - if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.https) { + if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.httpsWithToken)) { repoUrl = repoUrl.replace('localhost', 'artemis-app'); } if (cloneMethod == GitCloneMethod.https) { From 56351391d3c38dc71ca09935d6015739de2afdeb Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Thu, 5 Dec 2024 21:59:30 +0100 Subject: [PATCH 07/34] Accept the connection to ssh host and add it to known_hosts automatically --- docker/playwright.yml | 1 - .../support/pageobjects/exercises/programming/GitClient.ts | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/playwright.yml b/docker/playwright.yml index 85a0cfbf0872..df1969491dff 100644 --- a/docker/playwright.yml +++ b/docker/playwright.yml @@ -27,7 +27,6 @@ services: command: > sh -c ' ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -q -N ""; - ssh-keyscan -H -p 7921 artemis-app >> ~/.ssh/known_hosts; cd /app/artemis/src/test/playwright && mkdir -p ssh-keys; cp ~/.ssh/id_rsa.pub ./ssh-keys/id_rsa.pub; diff --git a/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts b/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts index 4f22636c07c2..fc02af82e85c 100644 --- a/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts +++ b/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts @@ -5,6 +5,8 @@ class GitClient { async cloneRepo(url: string, repoName: string) { const git = simpleGit(); const repoPath = `./${process.env.EXERCISE_REPO_DIRECTORY}/${repoName}`; + // Disable host key checking to avoid interactive prompt. Adds server to known_hosts. + git.env({ GIT_SSH_COMMAND: 'ssh -o StrictHostKeyChecking=no' }); if (!fs.existsSync(repoPath)) { fs.mkdirSync(repoPath, { recursive: true }); From 8cf9a868e43acf32d63172a13c67fa94897cf884 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Fri, 6 Dec 2024 01:08:25 +0100 Subject: [PATCH 08/34] Enable verbose mode --- src/test/playwright/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/playwright/package.json b/src/test/playwright/package.json index f3161e557b77..4ac9cd4ffdc5 100644 --- a/src/test/playwright/package.json +++ b/src/test/playwright/package.json @@ -16,8 +16,8 @@ }, "scripts": { "playwright:test": "npm-run-all --serial --continue-on-error playwright:test:parallel playwright:test:sequential merge-reports", - "playwright:test:parallel": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-parallel.xml playwright test e2e --project=fast-tests --project=slow-tests", - "playwright:test:sequential": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-sequential.xml playwright test e2e --project=sequential-tests --workers 1", + "playwright:test:parallel": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-parallel.xml DEBUG=pw:api playwright test e2e --project=fast-tests --project=slow-tests", + "playwright:test:sequential": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-sequential.xml DEBUG=pw:api playwright test e2e --project=sequential-tests --workers 1", "playwright:open": "playwright test e2e --ui", "playwright:setup": "npx playwright install --with-deps chromium && playwright test init", "merge-reports": "junit-merge ./test-reports/results-parallel.xml ./test-reports/results-sequential.xml -o ./test-reports/results.xml", From 8ecf5a4cc012b8078b37404fb2ab14ec3be16656 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Fri, 6 Dec 2024 01:56:24 +0100 Subject: [PATCH 09/34] Use container name instead of server url for ssh-based cloning --- .../programming/ProgrammingExerciseParticipation.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index fb9668c5b8a9..4d6e8c2a7aaa 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -282,6 +282,9 @@ async function makeGitExerciseSubmission( if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.httpsWithToken)) { repoUrl = repoUrl.replace('localhost', 'artemis-app'); } + if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.ssh) { + repoUrl = repoUrl.replace(/ls1Agent.*\.ase\.cit\.tum\.de/, 'artemis-app'); + } if (cloneMethod == GitCloneMethod.https) { repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); repoUrl = repoUrl.replace(`:**********`, ``); From f0a7d7c43bc41819f3af0d000fad1f3c0d151cf8 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 10 Dec 2024 19:16:03 +0100 Subject: [PATCH 10/34] Expose port 8080 to localhost machine on CI and use default Git url instead of container name in the URLs --- docker/playwright-E2E-tests-mysql.yml | 5 +++++ .../programming/ProgrammingExerciseParticipation.spec.ts | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docker/playwright-E2E-tests-mysql.yml b/docker/playwright-E2E-tests-mysql.yml index 4b9f212ba2e2..e72ddfac1f84 100644 --- a/docker/playwright-E2E-tests-mysql.yml +++ b/docker/playwright-E2E-tests-mysql.yml @@ -18,6 +18,9 @@ services: env_file: - ./artemis/config/playwright.env - ./artemis/config/playwright-mysql.env + ports: + - "8080:8080" + - "7921:7921" nginx: extends: @@ -44,6 +47,8 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' + extra_hosts: + - "localhost:host-gateway" networks: artemis: diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 4d6e8c2a7aaa..3d7817e3a4b2 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -279,9 +279,9 @@ async function makeGitExerciseSubmission( await programmingExerciseOverview.openCloneMenu(cloneMethod); } let repoUrl = await programmingExerciseOverview.copyCloneUrl(); - if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.httpsWithToken)) { - repoUrl = repoUrl.replace('localhost', 'artemis-app'); - } + // if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.httpsWithToken)) { + // repoUrl = repoUrl.replace('localhost', 'artemis-app'); + // } if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.ssh) { repoUrl = repoUrl.replace(/ls1Agent.*\.ase\.cit\.tum\.de/, 'artemis-app'); } From 82f31880032a884ba6f950834a47f7e676df6105 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 10 Dec 2024 19:52:39 +0100 Subject: [PATCH 11/34] Move port changes to correct compose file --- docker/playwright-E2E-tests-mysql-localci.yml | 5 +++++ docker/playwright-E2E-tests-mysql.yml | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/playwright-E2E-tests-mysql-localci.yml b/docker/playwright-E2E-tests-mysql-localci.yml index cf677b01fec6..3df7414f17c3 100644 --- a/docker/playwright-E2E-tests-mysql-localci.yml +++ b/docker/playwright-E2E-tests-mysql-localci.yml @@ -19,6 +19,9 @@ services: env_file: - ./artemis/config/playwright.env - ./artemis/config/playwright-local.env + ports: + - "8080:8080" + - "7921:7921" volumes: - /var/run/docker.sock:/var/run/docker.sock @@ -47,6 +50,8 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' + extra_hosts: + - "localhost:host-gateway" networks: artemis: diff --git a/docker/playwright-E2E-tests-mysql.yml b/docker/playwright-E2E-tests-mysql.yml index e72ddfac1f84..4b9f212ba2e2 100644 --- a/docker/playwright-E2E-tests-mysql.yml +++ b/docker/playwright-E2E-tests-mysql.yml @@ -18,9 +18,6 @@ services: env_file: - ./artemis/config/playwright.env - ./artemis/config/playwright-mysql.env - ports: - - "8080:8080" - - "7921:7921" nginx: extends: @@ -47,8 +44,6 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' - extra_hosts: - - "localhost:host-gateway" networks: artemis: From be846aca5af2a2765180f7e50d718374702bdfde Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 10 Dec 2024 20:56:47 +0100 Subject: [PATCH 12/34] Remove 7921 port mapping as it's mapped for nginx --- docker/playwright-E2E-tests-mysql-localci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/playwright-E2E-tests-mysql-localci.yml b/docker/playwright-E2E-tests-mysql-localci.yml index 3df7414f17c3..4c0aace30efb 100644 --- a/docker/playwright-E2E-tests-mysql-localci.yml +++ b/docker/playwright-E2E-tests-mysql-localci.yml @@ -21,7 +21,6 @@ services: - ./artemis/config/playwright-local.env ports: - "8080:8080" - - "7921:7921" volumes: - /var/run/docker.sock:/var/run/docker.sock From 275bcf6ddbdf26bf4f8fad74d117a2bc76a61476 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Tue, 10 Dec 2024 21:54:54 +0100 Subject: [PATCH 13/34] Switch playwright container to host network mode --- docker/playwright-E2E-tests-mysql-localci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/playwright-E2E-tests-mysql-localci.yml b/docker/playwright-E2E-tests-mysql-localci.yml index 4c0aace30efb..6ee5a8f3bfb1 100644 --- a/docker/playwright-E2E-tests-mysql-localci.yml +++ b/docker/playwright-E2E-tests-mysql-localci.yml @@ -49,8 +49,7 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' - extra_hosts: - - "localhost:host-gateway" + network_mode: host networks: artemis: From 8ffd2085ce287cc59ad271b142d4d7bbc641308f Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Wed, 11 Dec 2024 00:24:29 +0100 Subject: [PATCH 14/34] Revert "Switch playwright container to host network mode" This reverts commit 275bcf6ddbdf26bf4f8fad74d117a2bc76a61476. --- docker/playwright-E2E-tests-mysql-localci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/playwright-E2E-tests-mysql-localci.yml b/docker/playwright-E2E-tests-mysql-localci.yml index 6ee5a8f3bfb1..4c0aace30efb 100644 --- a/docker/playwright-E2E-tests-mysql-localci.yml +++ b/docker/playwright-E2E-tests-mysql-localci.yml @@ -49,7 +49,8 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' - network_mode: host + extra_hosts: + - "localhost:host-gateway" networks: artemis: From 50d3571635814915bce3d270c97fa17d2c07fd4b Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Wed, 11 Dec 2024 00:24:29 +0100 Subject: [PATCH 15/34] Revert "Remove 7921 port mapping as it's mapped for nginx" This reverts commit be846aca5af2a2765180f7e50d718374702bdfde. --- docker/playwright-E2E-tests-mysql-localci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/playwright-E2E-tests-mysql-localci.yml b/docker/playwright-E2E-tests-mysql-localci.yml index 4c0aace30efb..3df7414f17c3 100644 --- a/docker/playwright-E2E-tests-mysql-localci.yml +++ b/docker/playwright-E2E-tests-mysql-localci.yml @@ -21,6 +21,7 @@ services: - ./artemis/config/playwright-local.env ports: - "8080:8080" + - "7921:7921" volumes: - /var/run/docker.sock:/var/run/docker.sock From d0238fe780b07e26ecabd7356dfb3fef1762f23b Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Wed, 11 Dec 2024 00:24:29 +0100 Subject: [PATCH 16/34] Revert "Move port changes to correct compose file" This reverts commit 82f31880032a884ba6f950834a47f7e676df6105. --- docker/playwright-E2E-tests-mysql-localci.yml | 5 ----- docker/playwright-E2E-tests-mysql.yml | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/playwright-E2E-tests-mysql-localci.yml b/docker/playwright-E2E-tests-mysql-localci.yml index 3df7414f17c3..cf677b01fec6 100644 --- a/docker/playwright-E2E-tests-mysql-localci.yml +++ b/docker/playwright-E2E-tests-mysql-localci.yml @@ -19,9 +19,6 @@ services: env_file: - ./artemis/config/playwright.env - ./artemis/config/playwright-local.env - ports: - - "8080:8080" - - "7921:7921" volumes: - /var/run/docker.sock:/var/run/docker.sock @@ -50,8 +47,6 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' - extra_hosts: - - "localhost:host-gateway" networks: artemis: diff --git a/docker/playwright-E2E-tests-mysql.yml b/docker/playwright-E2E-tests-mysql.yml index 4b9f212ba2e2..e72ddfac1f84 100644 --- a/docker/playwright-E2E-tests-mysql.yml +++ b/docker/playwright-E2E-tests-mysql.yml @@ -18,6 +18,9 @@ services: env_file: - ./artemis/config/playwright.env - ./artemis/config/playwright-mysql.env + ports: + - "8080:8080" + - "7921:7921" nginx: extends: @@ -44,6 +47,8 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' + extra_hosts: + - "localhost:host-gateway" networks: artemis: From d0e8af4fcd183d21e911983d06525de44b823615 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Wed, 11 Dec 2024 00:24:30 +0100 Subject: [PATCH 17/34] Revert "Expose port 8080 to localhost machine on CI and use default Git url instead of container name in the URLs" This reverts commit f0a7d7c43bc41819f3af0d000fad1f3c0d151cf8. --- docker/playwright-E2E-tests-mysql.yml | 5 ----- .../programming/ProgrammingExerciseParticipation.spec.ts | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/docker/playwright-E2E-tests-mysql.yml b/docker/playwright-E2E-tests-mysql.yml index e72ddfac1f84..4b9f212ba2e2 100644 --- a/docker/playwright-E2E-tests-mysql.yml +++ b/docker/playwright-E2E-tests-mysql.yml @@ -18,9 +18,6 @@ services: env_file: - ./artemis/config/playwright.env - ./artemis/config/playwright-mysql.env - ports: - - "8080:8080" - - "7921:7921" nginx: extends: @@ -47,8 +44,6 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' - extra_hosts: - - "localhost:host-gateway" networks: artemis: diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 3d7817e3a4b2..4d6e8c2a7aaa 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -279,9 +279,9 @@ async function makeGitExerciseSubmission( await programmingExerciseOverview.openCloneMenu(cloneMethod); } let repoUrl = await programmingExerciseOverview.copyCloneUrl(); - // if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.httpsWithToken)) { - // repoUrl = repoUrl.replace('localhost', 'artemis-app'); - // } + if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.httpsWithToken)) { + repoUrl = repoUrl.replace('localhost', 'artemis-app'); + } if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.ssh) { repoUrl = repoUrl.replace(/ls1Agent.*\.ase\.cit\.tum\.de/, 'artemis-app'); } From a12367fc825885d2976b9bb4ef17545e5dc25fd5 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Wed, 11 Dec 2024 23:57:09 +0100 Subject: [PATCH 18/34] Remove git token test to delegate it to another branch --- .../ProgrammingExerciseParticipation.spec.ts | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 4d6e8c2a7aaa..236596bbacc8 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -97,21 +97,7 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = exercise = await exerciseAPIRequests.createProgrammingExercise({ course, programmingLanguage: ProgrammingLanguage.JAVA }); }); - test('Makes a git submission through HTTPS using token', async ({ programmingExerciseOverview, page }) => { - await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); - await makeGitExerciseSubmission( - page, - programmingExerciseOverview, - course, - exercise, - studentOne, - javaAllSuccessfulSubmission, - 'Solution', - GitCloneMethod.httpsWithToken, - ); - }); - - test('Makes a submission using SSH git', async ({ page, programmingExerciseOverview }) => { + test('Makes a git submission using SSH', async ({ page, programmingExerciseOverview }) => { await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); await makeGitExerciseSubmission(page, programmingExerciseOverview, course, exercise, studentOne, javaAllSuccessfulSubmission, 'Solution', GitCloneMethod.ssh); }); @@ -279,7 +265,7 @@ async function makeGitExerciseSubmission( await programmingExerciseOverview.openCloneMenu(cloneMethod); } let repoUrl = await programmingExerciseOverview.copyCloneUrl(); - if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.httpsWithToken)) { + if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.https) { repoUrl = repoUrl.replace('localhost', 'artemis-app'); } if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.ssh) { From bd7a698e52c999d610b4ee509945b3a65b84212a Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Thu, 12 Dec 2024 02:27:45 +0100 Subject: [PATCH 19/34] Changes to correspond to updated SSH settings of Artemis --- .../ssh-user-settings-key-details.component.html | 3 ++- .../ProgrammingExerciseParticipation.spec.ts | 2 +- .../requests/AccountManagementAPIRequests.ts | 14 +++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/shared/user-settings/ssh-settings/details/ssh-user-settings-key-details.component.html b/src/main/webapp/app/shared/user-settings/ssh-settings/details/ssh-user-settings-key-details.component.html index d1ecbd74bbc3..661cd1c3ae3d 100644 --- a/src/main/webapp/app/shared/user-settings/ssh-settings/details/ssh-user-settings-key-details.component.html +++ b/src/main/webapp/app/shared/user-settings/ssh-settings/details/ssh-user-settings-key-details.component.html @@ -27,7 +27,7 @@

}
- +
@@ -121,6 +121,7 @@

[icon]="faSave" [title]="'artemisApp.userSettings.sshSettingsPage.saveSshKey'" (onClick)="saveSshKey()" + data-testid="saveSshKeyButton" /> } diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 236596bbacc8..f994d3bcf026 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -295,7 +295,7 @@ async function setupSSHCredentials(context: BrowserContext) { await page.goto('user-settings/ssh'); await page.getByTestId('addNewSshKeyButton').click(); await page.getByTestId('sshKeyField').fill(sshKey!); - const responsePromise = page.waitForResponse(`${BASE_API}/account/ssh-public-key`); + const responsePromise = page.waitForResponse(`${BASE_API}/ssh-settings/public-key`); await page.getByTestId('saveSshKeyButton').click(); await responsePromise; } diff --git a/src/test/playwright/support/requests/AccountManagementAPIRequests.ts b/src/test/playwright/support/requests/AccountManagementAPIRequests.ts index 12bfa5e14a7e..60e5c6b8fb32 100644 --- a/src/test/playwright/support/requests/AccountManagementAPIRequests.ts +++ b/src/test/playwright/support/requests/AccountManagementAPIRequests.ts @@ -3,12 +3,24 @@ import { Page } from '@playwright/test'; export class AccountManagementAPIRequests { private readonly page: Page; + private readonly PLAYWRIGHT_SSH_LABEL = 'playwright-ssh'; constructor(page: Page) { this.page = page; } + /** + * Deletes the existing test SSH public keys. + * Currently, the API does not return the ID of the created SSH public key. + * As a workaround, we fetch all SSH public keys and delete the test SSH key based on the label. + * */ async deleteSshPublicKey() { - return await this.page.request.delete(`${BASE_API}/account/ssh-public-key`); + const publicKeysResponse = await this.page.request.get(`${BASE_API}/ssh-settings/public-keys`); + const publicKeys = await publicKeysResponse.json(); + for (const publicKey of publicKeys) { + if (publicKey.label === this.PLAYWRIGHT_SSH_LABEL) { + await this.page.request.delete(`${BASE_API}/ssh-settings/public-key/${publicKey.id}`); + } + } } } From 30d7157004817f5042f27de298ab06da60ddbe58 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Thu, 12 Dec 2024 02:29:04 +0100 Subject: [PATCH 20/34] Use pre-generated ssh certificates during e2e testing --- docker/playwright.yml | 7 +-- src/test/playwright/ssh-keys/id_ed25519 | 7 +++ src/test/playwright/ssh-keys/id_ed25519.pub | 1 + src/test/playwright/ssh-keys/id_rsa | 49 +++++++++++++++++++++ src/test/playwright/ssh-keys/id_rsa.pub | 1 + 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/test/playwright/ssh-keys/id_ed25519 create mode 100644 src/test/playwright/ssh-keys/id_ed25519.pub create mode 100644 src/test/playwright/ssh-keys/id_rsa create mode 100644 src/test/playwright/ssh-keys/id_rsa.pub diff --git a/docker/playwright.yml b/docker/playwright.yml index df1969491dff..30de367a1747 100644 --- a/docker/playwright.yml +++ b/docker/playwright.yml @@ -26,10 +26,11 @@ services: FAST_TEST_TIMEOUT_SECONDS: '${bamboo_fast_test_timeout_seconds}' command: > sh -c ' - ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -q -N ""; cd /app/artemis/src/test/playwright && - mkdir -p ssh-keys; - cp ~/.ssh/id_rsa.pub ./ssh-keys/id_rsa.pub; + cp ./ssh-keys/id_rsa.pub ~/.ssh/id_rsa.pub; + cp ./ssh-keys/id_rsa ~/.ssh/id_rsa; + cp ./ssh-keys/id_ed25519.pub ~/.ssh/id_ed25519.pub; + cp ./ssh-keys/id_ed25519 ~/.ssh/id_ed25519; chmod 777 /root && npm ci && npm run playwright:setup && diff --git a/src/test/playwright/ssh-keys/id_ed25519 b/src/test/playwright/ssh-keys/id_ed25519 new file mode 100644 index 000000000000..3840417cf786 --- /dev/null +++ b/src/test/playwright/ssh-keys/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBXUrxF0OssKvwEp5h6dl60T/8pnhQ4oPpZ3vlSgO4mbgAAAJivy8zSr8vM +0gAAAAtzc2gtZWQyNTUxOQAAACBXUrxF0OssKvwEp5h6dl60T/8pnhQ4oPpZ3vlSgO4mbg +AAAEAkPPHHQMUBm7z8JDkhu+eGeZEyC4zQAimS3V6ExkYSL1dSvEXQ6ywq/ASnmHp2XrRP +/ymeFDig+lne+VKA7iZuAAAADnBsYXl3cmlnaHQtc3NoAQIDBAUGBw== +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/playwright/ssh-keys/id_ed25519.pub b/src/test/playwright/ssh-keys/id_ed25519.pub new file mode 100644 index 000000000000..0b1a06d3f9aa --- /dev/null +++ b/src/test/playwright/ssh-keys/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFdSvEXQ6ywq/ASnmHp2XrRP/ymeFDig+lne+VKA7iZu playwright-ssh diff --git a/src/test/playwright/ssh-keys/id_rsa b/src/test/playwright/ssh-keys/id_rsa new file mode 100644 index 000000000000..7e4e81dfa6ac --- /dev/null +++ b/src/test/playwright/ssh-keys/id_rsa @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/playwright/ssh-keys/id_rsa.pub b/src/test/playwright/ssh-keys/id_rsa.pub new file mode 100644 index 000000000000..423db0eeaaa8 --- /dev/null +++ b/src/test/playwright/ssh-keys/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDT05Fq7t7n4cBvHIYkP8tOjXheCKajmXL6MF2xkZsW4cmZW7ecTB/8mx2D8gm5ERr9hhTN6mAmO980l5C2mGTH9ZjiF3TMoucYth7M7wdR849NS+WMfqZIN3L9ym3qn9X8oc9eT78ov67FnBe0f5EjMlZztw2E3buiUZ/KTCoTtvhnThBYIDLwO10ZfPorrWabW6iaON7sHqVVJCLUeAsxz7rxf7g4SORltW0DFX9Int2IJ4ValV4JcHE2AdEN1KD8zoSmV4GTUtVBgsHklzYVMrzRmX1DUGLt/1a/8j3b57aZZviFAX1LdmsrhDqzK908I1xzljk6R/8VEYLBGw//FlwErcuV9yGXBF8ha33ST9yxKLQ4KC6LsgN5e/7OdISp4eplO65XPOEdol+dYz/7PWguR6oVKykYoKehIf6ZOoyQgApfToY03rrmpBad4xonzPPsOuUgzHSZ9EaAOatalXKz8jfWOQDE4EOrpKg1jJILPx2+eVZM1IRntjwS8pdtiEY3FZsnXKQ7OuaoS//Bt9DO1I+M7hIKrdJ7Hod3NXbTXUIRA7BCzHrXYX9NBDgPKEN13P8ww2MuBe3GiT9SJZYQkXb7VFR7I7tsH7pEVSyouC4U4i7gEbO7ZBIIolzpnMJl1x/2IAgjm8oh+DaBBit4e/DXYqB+lLwJCRvcqw== playwright-ssh From 293be08faa8f93cee7e4250302f233a77a1c8b8c Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Thu, 12 Dec 2024 03:03:47 +0100 Subject: [PATCH 21/34] Revert "Use pre-generated ssh certificates during e2e testing" This reverts commit 30d7157004817f5042f27de298ab06da60ddbe58. --- docker/playwright.yml | 7 ++- src/test/playwright/ssh-keys/id_ed25519 | 7 --- src/test/playwright/ssh-keys/id_ed25519.pub | 1 - src/test/playwright/ssh-keys/id_rsa | 49 --------------------- src/test/playwright/ssh-keys/id_rsa.pub | 1 - 5 files changed, 3 insertions(+), 62 deletions(-) delete mode 100644 src/test/playwright/ssh-keys/id_ed25519 delete mode 100644 src/test/playwright/ssh-keys/id_ed25519.pub delete mode 100644 src/test/playwright/ssh-keys/id_rsa delete mode 100644 src/test/playwright/ssh-keys/id_rsa.pub diff --git a/docker/playwright.yml b/docker/playwright.yml index 30de367a1747..df1969491dff 100644 --- a/docker/playwright.yml +++ b/docker/playwright.yml @@ -26,11 +26,10 @@ services: FAST_TEST_TIMEOUT_SECONDS: '${bamboo_fast_test_timeout_seconds}' command: > sh -c ' + ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -q -N ""; cd /app/artemis/src/test/playwright && - cp ./ssh-keys/id_rsa.pub ~/.ssh/id_rsa.pub; - cp ./ssh-keys/id_rsa ~/.ssh/id_rsa; - cp ./ssh-keys/id_ed25519.pub ~/.ssh/id_ed25519.pub; - cp ./ssh-keys/id_ed25519 ~/.ssh/id_ed25519; + mkdir -p ssh-keys; + cp ~/.ssh/id_rsa.pub ./ssh-keys/id_rsa.pub; chmod 777 /root && npm ci && npm run playwright:setup && diff --git a/src/test/playwright/ssh-keys/id_ed25519 b/src/test/playwright/ssh-keys/id_ed25519 deleted file mode 100644 index 3840417cf786..000000000000 --- a/src/test/playwright/ssh-keys/id_ed25519 +++ /dev/null @@ -1,7 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW -QyNTUxOQAAACBXUrxF0OssKvwEp5h6dl60T/8pnhQ4oPpZ3vlSgO4mbgAAAJivy8zSr8vM -0gAAAAtzc2gtZWQyNTUxOQAAACBXUrxF0OssKvwEp5h6dl60T/8pnhQ4oPpZ3vlSgO4mbg -AAAEAkPPHHQMUBm7z8JDkhu+eGeZEyC4zQAimS3V6ExkYSL1dSvEXQ6ywq/ASnmHp2XrRP -/ymeFDig+lne+VKA7iZuAAAADnBsYXl3cmlnaHQtc3NoAQIDBAUGBw== ------END OPENSSH PRIVATE KEY----- diff --git a/src/test/playwright/ssh-keys/id_ed25519.pub b/src/test/playwright/ssh-keys/id_ed25519.pub deleted file mode 100644 index 0b1a06d3f9aa..000000000000 --- a/src/test/playwright/ssh-keys/id_ed25519.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFdSvEXQ6ywq/ASnmHp2XrRP/ymeFDig+lne+VKA7iZu playwright-ssh diff --git a/src/test/playwright/ssh-keys/id_rsa b/src/test/playwright/ssh-keys/id_rsa deleted file mode 100644 index 7e4e81dfa6ac..000000000000 --- a/src/test/playwright/ssh-keys/id_rsa +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn -NhAAAAAwEAAQAAAgEA09ORau7e5+HAbxyGJD/LTo14Xgimo5ly+jBdsZGbFuHJmVu3nEwf -/Jsdg/IJuREa/YYUzepgJjvfNJeQtphkx/WY4hd0zKLnGLYezO8HUfOPTUvljH6mSDdy/c -pt6p/V/KHPXk+/KL+uxZwXtH+RIzJWc7cNhN27olGfykwqE7b4Z04QWCAy8DtdGXz6K61m -m1uomjje7B6lVSQi1HgLMc+68X+4OEjkZbVtAxV/SJ7diCeFWpVeCXBxNgHRDdSg/M6Epl -eBk1LVQYLB5Jc2FTK80Zl9Q1Bi7f9Wv/I92+e2mWb4hQF9S3ZrK4Q6syvdPCNcc5Y5Okf/ -FRGCwRsP/xZcBK3LlfchlwRfIWt90k/csSi0OCgui7IDeXv+znSEqeHqZTuuVzzhHaJfnW -M/+z1oLkeqFSspGKCnoSH+mTqMkIAKX06GNN665qQWneMaJ8zz7DrlIMx0mfRGgDmrWpVy -s/I31jkAxOBDq6SoNYySCz8dvnlWTNSEZ7Y8EvKXbYhGNxWbJ1ykOzrmqEv/wbfQztSPjO -4SCq3Sex6HdzV2011CEQOwQsx612F/TQQ4DyhDddz/MMNjLgXtxok/UiWWEJF2+1RUeyO7 -bB+6RFUsqLguFOIu4BGzu2QSCKJc6ZzCZdcf9iAII5vKIfg2gQYreHvw12KgfpS8CQkb3K -sAAAdIFOtxlRTrcZUAAAAHc3NoLXJzYQAAAgEA09ORau7e5+HAbxyGJD/LTo14Xgimo5ly -+jBdsZGbFuHJmVu3nEwf/Jsdg/IJuREa/YYUzepgJjvfNJeQtphkx/WY4hd0zKLnGLYezO -8HUfOPTUvljH6mSDdy/cpt6p/V/KHPXk+/KL+uxZwXtH+RIzJWc7cNhN27olGfykwqE7b4 -Z04QWCAy8DtdGXz6K61mm1uomjje7B6lVSQi1HgLMc+68X+4OEjkZbVtAxV/SJ7diCeFWp -VeCXBxNgHRDdSg/M6EpleBk1LVQYLB5Jc2FTK80Zl9Q1Bi7f9Wv/I92+e2mWb4hQF9S3Zr -K4Q6syvdPCNcc5Y5Okf/FRGCwRsP/xZcBK3LlfchlwRfIWt90k/csSi0OCgui7IDeXv+zn -SEqeHqZTuuVzzhHaJfnWM/+z1oLkeqFSspGKCnoSH+mTqMkIAKX06GNN665qQWneMaJ8zz -7DrlIMx0mfRGgDmrWpVys/I31jkAxOBDq6SoNYySCz8dvnlWTNSEZ7Y8EvKXbYhGNxWbJ1 -ykOzrmqEv/wbfQztSPjO4SCq3Sex6HdzV2011CEQOwQsx612F/TQQ4DyhDddz/MMNjLgXt -xok/UiWWEJF2+1RUeyO7bB+6RFUsqLguFOIu4BGzu2QSCKJc6ZzCZdcf9iAII5vKIfg2gQ -YreHvw12KgfpS8CQkb3KsAAAADAQABAAACAQC6dDVBMgflhlJTu4Db8YdsmpxD8Ut1IAIT -9XTTc35XUuGYayIgouVY/dSqKueiDkt6s4YmhtKhdPwRGASapWO4mxc6lDkMU6wllVFhRF -lmKoSOHmttEqIdWSmP+Zps9spt+KRGuN1j6e3WJYK8L7m6iHmOm6d7t42M4FpNwGMdPWZD -0HQUjisoSKi3Ycn3nkd+It0y+zJGFuejHQ7HhO8dpmNAZhLrAEaCvIK7PzXt32DSky8PwA -CpQF92kXzJtssKHGKo5Me6vSPX6VfwdaV2XVXVDU00blLWeXaLuJf4tGqHdxr0QI5gWVkW -Ues1gTvYzqt3VtkC0xLWIba9cavOXFCOtNsFEM8OpJy2IBzSk30tUo3LoxPUuEYu5Niih1 -+EDJ8u1soc+eAEl9yTJjZP05WqcjQjMRv0EpRBtgb90bCZMVWuaubyt0qdFDtWUl+oJZme -MuW0i3n88jn/xSs/eZxl3k4VYKPrdsEx9hM311HJ2lTNLU1wTe9nz/rf2Y6SQhLnohhmOD -KvJXlZgxc1yRaDd/y7rJHf+k41G2YcMJVX5K0gecnXMLAFlxRt/sSjIV+5+1JoG/hkv9Zu -bwH73kdmpV6lPwtobtgiV7393lVGHZYkLanVdAQjRjnSjzWIaMH1nZ4u/ImzEAWT1y9Fc9 -wjUVeby+3V8kTMJOWoQQAAAQAGFb9oVZEJ8Az41k4kwPWzQ7dQHkS9lQR0eG5eIz9+5i4g -IdhI9CbHFyDgAzNX3FwA9OJcXh5RKGPhJgp63ADn/uCiUr05XB3kgqgxhzik2X4UtE/ROb -4sVGGmEDG22+XW8EFsS0zLThm3s+OUi5/yzyyMdDsBR76G4C3xIqPsa9ZVBJ7NKe/qncAz -ZfkDGr+ovKN8fmnTTak7ACf9FrXfb3V6qadkNNxPTjQDlOZNxDJzBCeA75yCkf5eoDAD4g -zc9om9iYxbmfKsLe67VbfBioHiiomWC16tCv6blzx76FK15Ruj9+uMuO4cV7GLDZg9sv3B -We50Xod76WblIEedAAABAQDxYPbir8bw6qtJpBhflcF40NQgX/DjdB0BbR7SboNIzGISe3 -9JkR2ByVbR3/DNfFIkKT4GRpZoW8Nzi3E1gx5eJc3pQsH2YWDN3BQE24jIJmhvKcn2ZCQ1 -RPTXO6v+1wC4KHtfx6S9e7ujeiT+5vck9URq8C6CslRuthbp/BEOzbcMj9wopvzRsjOzIq -e5JuzLCjFTHf3FMoyorufW5JYrT9ZD/C296srxf5yGmoR/l9540eMRROraY3qmQocHqYWj -C/Iq0bT9cAyr1bhoKLMdcLGtRlomih3RM14zTwWBqQxRp6QuIX0rWd+PwM6BjZoPUvbWK4 -2zpHphGnevpceLAAABAQDgqFa2aGQiFmK/JiZ4Mk5fU3eaziNPXQp/mXpCSmXFQ/hJDGjN -M4Rvkkhn6enks2eo/tSrxsdsZgshUCkmh4HDVvHMSB6RmiRLdd95cQr9D0JbxX4s5T8iOi -EluioJh9G/BmxpHT1wYlG1DA2sZtxUCOSCBgrBWpOEbFX5mKSlgJs14RnOYcyX1mYEnWcf -mZ8AJlJRtvwYkz/a+sb+nLwPsnXYW41akvd+7ah2XrANSsS+/6IA9OTMIQwPmjb+HqDYh0 -iK8xDNy1RcKKvQYguS3NVXz2Oza59SuqJ9FyqDNzJXZl11MpYt9uiyhRhiid7lVhJQ12t5 -fBKLQ6wVceNhAAAADnBsYXl3cmlnaHQtc3NoAQIDBA== ------END OPENSSH PRIVATE KEY----- diff --git a/src/test/playwright/ssh-keys/id_rsa.pub b/src/test/playwright/ssh-keys/id_rsa.pub deleted file mode 100644 index 423db0eeaaa8..000000000000 --- a/src/test/playwright/ssh-keys/id_rsa.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDT05Fq7t7n4cBvHIYkP8tOjXheCKajmXL6MF2xkZsW4cmZW7ecTB/8mx2D8gm5ERr9hhTN6mAmO980l5C2mGTH9ZjiF3TMoucYth7M7wdR849NS+WMfqZIN3L9ym3qn9X8oc9eT78ov67FnBe0f5EjMlZztw2E3buiUZ/KTCoTtvhnThBYIDLwO10ZfPorrWabW6iaON7sHqVVJCLUeAsxz7rxf7g4SORltW0DFX9Int2IJ4ValV4JcHE2AdEN1KD8zoSmV4GTUtVBgsHklzYVMrzRmX1DUGLt/1a/8j3b57aZZviFAX1LdmsrhDqzK908I1xzljk6R/8VEYLBGw//FlwErcuV9yGXBF8ha33ST9yxKLQ4KC6LsgN5e/7OdISp4eplO65XPOEdol+dYz/7PWguR6oVKykYoKehIf6ZOoyQgApfToY03rrmpBad4xonzPPsOuUgzHSZ9EaAOatalXKz8jfWOQDE4EOrpKg1jJILPx2+eVZM1IRntjwS8pdtiEY3FZsnXKQ7OuaoS//Bt9DO1I+M7hIKrdJ7Hod3NXbTXUIRA7BCzHrXYX9NBDgPKEN13P8ww2MuBe3GiT9SJZYQkXb7VFR7I7tsH7pEVSyouC4U4i7gEbO7ZBIIolzpnMJl1x/2IAgjm8oh+DaBBit4e/DXYqB+lLwJCRvcqw== playwright-ssh From 754573a5b46f41beac60370c447998b3e94d4191 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Thu, 12 Dec 2024 22:26:37 +0100 Subject: [PATCH 22/34] Set up pre-generated ssh keys using a global playwright script --- docker/playwright.yml | 3 -- .../ProgrammingExerciseParticipation.spec.ts | 30 +++++++++--- src/test/playwright/init/global-setup.ts | 46 +++++++++++++++++ src/test/playwright/package.json | 4 +- src/test/playwright/playwright.config.ts | 1 + .../ssh-keys/artemis_playwright_ed25519 | 7 +++ .../ssh-keys/artemis_playwright_ed25519.pub | 1 + .../ssh-keys/artemis_playwright_rsa | 49 +++++++++++++++++++ .../ssh-keys/artemis_playwright_rsa.pub | 1 + .../exercises/programming/GitClient.ts | 17 +++++-- .../requests/AccountManagementAPIRequests.ts | 2 +- 11 files changed, 143 insertions(+), 18 deletions(-) create mode 100644 src/test/playwright/init/global-setup.ts create mode 100644 src/test/playwright/ssh-keys/artemis_playwright_ed25519 create mode 100644 src/test/playwright/ssh-keys/artemis_playwright_ed25519.pub create mode 100644 src/test/playwright/ssh-keys/artemis_playwright_rsa create mode 100644 src/test/playwright/ssh-keys/artemis_playwright_rsa.pub diff --git a/docker/playwright.yml b/docker/playwright.yml index df1969491dff..a27ba2f9d589 100644 --- a/docker/playwright.yml +++ b/docker/playwright.yml @@ -26,10 +26,7 @@ services: FAST_TEST_TIMEOUT_SECONDS: '${bamboo_fast_test_timeout_seconds}' command: > sh -c ' - ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa -q -N ""; cd /app/artemis/src/test/playwright && - mkdir -p ssh-keys; - cp ~/.ssh/id_rsa.pub ./ssh-keys/id_rsa.pub; chmod 777 /root && npm ci && npm run playwright:setup && diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index f994d3bcf026..bebe9a9a22f1 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -20,6 +20,7 @@ import { UserCredentials, admin, instructor, studentFour, studentOne, studentTwo import { Team } from 'app/entities/team.model'; import { GitCloneMethod, ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; import { Participation } from 'app/entities/participation/participation.model'; +import { SSH_KEY_NAMES, SshEncryptionAlgorithm } from '../../../init/global-setup'; test.describe('Programming exercise participation', { tag: '@sequential' }, () => { let course: Course; @@ -97,9 +98,19 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = exercise = await exerciseAPIRequests.createProgrammingExercise({ course, programmingLanguage: ProgrammingLanguage.JAVA }); }); - test('Makes a git submission using SSH', async ({ page, programmingExerciseOverview }) => { + test('Makes a git submission using SSH with RSA key', async ({ page, programmingExerciseOverview }) => { await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); - await makeGitExerciseSubmission(page, programmingExerciseOverview, course, exercise, studentOne, javaAllSuccessfulSubmission, 'Solution', GitCloneMethod.ssh); + await makeGitExerciseSubmission( + page, + programmingExerciseOverview, + course, + exercise, + studentOne, + javaAllSuccessfulSubmission, + 'Solution', + GitCloneMethod.ssh, + SshEncryptionAlgorithm.rsa, + ); }); test.afterEach('Delete SSH key', async ({ accountManagementAPIRequests }) => { @@ -254,13 +265,14 @@ async function makeGitExerciseSubmission( submission: any, commitMessage: string, cloneMethod: GitCloneMethod = GitCloneMethod.https, + sshAlgorithm?: SshEncryptionAlgorithm, ) { await programmingExerciseOverview.openCloneMenu(cloneMethod); - if (cloneMethod == GitCloneMethod.ssh) { + if (cloneMethod == GitCloneMethod.ssh && sshAlgorithm !== undefined) { await expect(programmingExerciseOverview.getCloneUrlButton()).toBeDisabled(); const sshKeyNotFoundAlert = page.locator('.alert', { hasText: 'To use ssh, you need to add an ssh key to your account' }); await expect(sshKeyNotFoundAlert).toBeVisible(); - await setupSSHCredentials(page.context()); + await setupSSHCredentials(page.context(), sshAlgorithm); await page.reload(); await programmingExerciseOverview.openCloneMenu(cloneMethod); } @@ -278,7 +290,8 @@ async function makeGitExerciseSubmission( console.log(`Cloning repository from ${repoUrl}`); const urlParts = repoUrl.split('/'); const repoName = urlParts[urlParts.length - 1]; - const exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName); + const sshKeyName = sshAlgorithm !== undefined ? SSH_KEY_NAMES[sshAlgorithm] : undefined; + const exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName, sshKeyName); console.log(`Cloned repository successfully. Pushing files...`); await pushGitSubmissionFiles(exerciseRepo, repoName, student, submission, commitMessage); await fs.rmdir(`./test-exercise-repos/${repoName}`, { recursive: true }); @@ -287,10 +300,11 @@ async function makeGitExerciseSubmission( await expect(resultScore.getByText(submission.expectedResult)).toBeVisible(); } -async function setupSSHCredentials(context: BrowserContext) { +async function setupSSHCredentials(context: BrowserContext, sshAlgorithm: SshEncryptionAlgorithm) { + console.log(`Setting up SSH credentials with key ${SSH_KEY_NAMES[sshAlgorithm]}`); const page = await context.newPage(); - const projectRoot = process.cwd(); - const sshKeyPath = path.join(projectRoot, 'ssh-keys', 'id_rsa.pub'); + const playwrightRoot = process.cwd(); + const sshKeyPath = path.join(playwrightRoot, 'ssh-keys', `${SSH_KEY_NAMES[sshAlgorithm]}.pub`); const sshKey = await fs.readFile(sshKeyPath, 'utf8'); await page.goto('user-settings/ssh'); await page.getByTestId('addNewSshKeyButton').click(); diff --git a/src/test/playwright/init/global-setup.ts b/src/test/playwright/init/global-setup.ts new file mode 100644 index 000000000000..a81db9168965 --- /dev/null +++ b/src/test/playwright/init/global-setup.ts @@ -0,0 +1,46 @@ +import fs from 'fs'; +import path from 'path'; +import os from 'os'; + +export enum SshEncryptionAlgorithm { + rsa, + ed25519, +} + +export const SSH_KEY_NAMES = { + [SshEncryptionAlgorithm.rsa]: 'artemis_playwright_rsa', + [SshEncryptionAlgorithm.ed25519]: 'artemis_playwright_ed25519', +}; + +async function globalSetup() { + console.log('Running global setup...'); + + const sshDir = path.join(os.homedir(), '.ssh'); + + for (const keyName of Object.values(SSH_KEY_NAMES)) { + const sourceKey = path.join(process.cwd(), 'ssh-keys', keyName); + const sourceKeyPub = path.join(process.cwd(), 'ssh-keys', `${keyName}.pub`); + const destKey = path.join(sshDir, keyName); + const destKeyPub = path.join(sshDir, `${keyName}.pub`); + + if (!fs.existsSync(destKey)) { + fs.copyFileSync(sourceKey, destKey); + fs.chmodSync(destKey, 0o600); + console.log(`Private SSH key ${keyName} copied.`); + } else { + console.log(`Private SSH key ${keyName} already exists, skipping.`); + } + + if (!fs.existsSync(destKeyPub)) { + fs.copyFileSync(sourceKeyPub, destKeyPub); + fs.chmodSync(destKeyPub, 0o644); + console.log(`Public SSH key ${keyName} copied.`); + } else { + console.log(`Public SSH key ${keyName} already exists, skipping.`); + } + } + + console.log('Global setup completed.'); +} + +export default globalSetup; diff --git a/src/test/playwright/package.json b/src/test/playwright/package.json index 489090a975b4..0624528ed7e7 100644 --- a/src/test/playwright/package.json +++ b/src/test/playwright/package.json @@ -16,8 +16,8 @@ }, "scripts": { "playwright:test": "npm-run-all --serial --continue-on-error playwright:test:parallel playwright:test:sequential merge-reports", - "playwright:test:parallel": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-parallel.xml DEBUG=pw:api playwright test e2e --project=fast-tests --project=slow-tests", - "playwright:test:sequential": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-sequential.xml DEBUG=pw:api playwright test e2e --project=sequential-tests --workers 1", + "playwright:test:parallel": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-parallel.xml playwright test e2e --project=fast-tests --project=slow-tests", + "playwright:test:sequential": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-sequential.xml Dplaywright test e2e --project=sequential-tests --workers 1", "playwright:open": "playwright test e2e --ui", "playwright:setup": "npx playwright install --with-deps chromium && playwright test init", "playwright:setup-local": "npx playwright install --with-deps chromium", diff --git a/src/test/playwright/playwright.config.ts b/src/test/playwright/playwright.config.ts index a963e42fadcb..b0d4124cec66 100644 --- a/src/test/playwright/playwright.config.ts +++ b/src/test/playwright/playwright.config.ts @@ -20,6 +20,7 @@ export default defineConfig({ workers: parseNumber(process.env.TEST_WORKER_PROCESSES) ?? 3, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [['junit', { outputFile: process.env.PLAYWRIGHT_JUNIT_OUTPUT_NAME ?? './test-reports/results.xml' }]], + globalSetup: require.resolve('./init/global-setup.ts'), /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { diff --git a/src/test/playwright/ssh-keys/artemis_playwright_ed25519 b/src/test/playwright/ssh-keys/artemis_playwright_ed25519 new file mode 100644 index 000000000000..0a958e019ff9 --- /dev/null +++ b/src/test/playwright/ssh-keys/artemis_playwright_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACA/OcbXN1QDtJhP1seN+9NmL1HuMnyBxOawYkPQJHqq0gAAAKCY71cGmO9X +BgAAAAtzc2gtZWQyNTUxOQAAACA/OcbXN1QDtJhP1seN+9NmL1HuMnyBxOawYkPQJHqq0g +AAAEC6t3RGmhqZySk/v7b9YbncJW1LVVoz4qPae+ktj+HwKz85xtc3VAO0mE/Wx43702Yv +Ue4yfIHE5rBiQ9AkeqrSAAAAFmFydGVtaXNfcGxheXdyaWdodF9zc2gBAgMEBQYH +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/playwright/ssh-keys/artemis_playwright_ed25519.pub b/src/test/playwright/ssh-keys/artemis_playwright_ed25519.pub new file mode 100644 index 000000000000..f55562972537 --- /dev/null +++ b/src/test/playwright/ssh-keys/artemis_playwright_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID85xtc3VAO0mE/Wx43702YvUe4yfIHE5rBiQ9AkeqrS artemis_playwright_ssh diff --git a/src/test/playwright/ssh-keys/artemis_playwright_rsa b/src/test/playwright/ssh-keys/artemis_playwright_rsa new file mode 100644 index 000000000000..bb7271b793fe --- /dev/null +++ b/src/test/playwright/ssh-keys/artemis_playwright_rsa @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/playwright/ssh-keys/artemis_playwright_rsa.pub b/src/test/playwright/ssh-keys/artemis_playwright_rsa.pub new file mode 100644 index 000000000000..31d49f3128ee --- /dev/null +++ b/src/test/playwright/ssh-keys/artemis_playwright_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC/v5RnrbWY09t28SFEGc1g535+3C7FX0XwFFG0dQbzuUdrZhcB4RrV2czhuiZVWzw0lio7wTDof6K+G6CjbUQ7vFK8JWSH8pN9edRYossWV87ZNTE1aUG73GfznmoOzrDXXei6S/FTk3buoud5dj+DJllkvB/3rFOn0CZW2xrdJhUvaAtKRDVLRQZlrUeOxWpoSRDOnRomHN1b+V+YYHg7CFY/MxsOpPYwr8Bk42HZlGoMgK3m1HTotiE6exK7Rvo7tT2wd7zp9HNh+sAYrlxDqXW6G0QQNblEavcOMccWq5XqE1xuaFaeCR9LZLyenYuXPj7TUwoWcUI9DT94QUJmoEbtP37HzGpFxvqIWWJuGlZWYBanz/U6rjmckAjh/n17FcLOCIfrQT226XybripcqLBPVBvV7odurkfr2+rrw4NaBa1tetyFDfxE+pebWZEzMgtc7TJLpyTyYHLltOPLtRImH6w9hlOX7Jtn6Azk7n2PZ7ctFmq+MiASBQ/pVwY6f3lh7o+fLYt+dOkdQTdDq2bygub6n9/rHLNH0LWqOgKFEEAgfh9CcdWJvGuSsQWrynPfG7WboRsHafLKAaCQLqVhSbMWcIjY5zmDIerhcC8tjIRAqgcW/tCHugg+0JB59ayC18FQNH3baOMsm0C2oYZfA6TomYFRkldErByheQ== artemis_playwright_ssh diff --git a/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts b/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts index fc02af82e85c..cfc87d5753f3 100644 --- a/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts +++ b/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts @@ -2,18 +2,27 @@ import { simpleGit } from 'simple-git'; import * as fs from 'fs'; class GitClient { - async cloneRepo(url: string, repoName: string) { + async cloneRepo(url: string, repoName: string, sshKeyName?: string) { const git = simpleGit(); const repoPath = `./${process.env.EXERCISE_REPO_DIRECTORY}/${repoName}`; - // Disable host key checking to avoid interactive prompt. Adds server to known_hosts. - git.env({ GIT_SSH_COMMAND: 'ssh -o StrictHostKeyChecking=no' }); + const gitSshCommand = sshKeyName ? `ssh -i ~/.ssh/${sshKeyName} -o StrictHostKeyChecking=no` : undefined; + + if (gitSshCommand) { + git.env({ GIT_SSH_COMMAND: gitSshCommand }); + } if (!fs.existsSync(repoPath)) { fs.mkdirSync(repoPath, { recursive: true }); } await git.clone(url, repoPath); - return simpleGit(`./${process.env.EXERCISE_REPO_DIRECTORY}/${repoName}`); + const clonedRepo = simpleGit(repoPath); + + if (gitSshCommand) { + clonedRepo.env({ GIT_SSH_COMMAND: gitSshCommand }); + } + + return clonedRepo; } } diff --git a/src/test/playwright/support/requests/AccountManagementAPIRequests.ts b/src/test/playwright/support/requests/AccountManagementAPIRequests.ts index 60e5c6b8fb32..5a8df6a606e4 100644 --- a/src/test/playwright/support/requests/AccountManagementAPIRequests.ts +++ b/src/test/playwright/support/requests/AccountManagementAPIRequests.ts @@ -3,7 +3,7 @@ import { Page } from '@playwright/test'; export class AccountManagementAPIRequests { private readonly page: Page; - private readonly PLAYWRIGHT_SSH_LABEL = 'playwright-ssh'; + private readonly PLAYWRIGHT_SSH_LABEL = 'artemis_playwright_ssh'; constructor(page: Page) { this.page = page; From 580431b182b593066af2c36b148843bf67dd1a8c Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Thu, 12 Dec 2024 22:41:49 +0100 Subject: [PATCH 23/34] Create .ssh folder if it does not exist --- src/test/playwright/init/global-setup.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/playwright/init/global-setup.ts b/src/test/playwright/init/global-setup.ts index a81db9168965..c3676c21b14f 100644 --- a/src/test/playwright/init/global-setup.ts +++ b/src/test/playwright/init/global-setup.ts @@ -16,6 +16,9 @@ async function globalSetup() { console.log('Running global setup...'); const sshDir = path.join(os.homedir(), '.ssh'); + if (!fs.existsSync(sshDir)) { + fs.mkdirSync(sshDir, { recursive: true }); + } for (const keyName of Object.values(SSH_KEY_NAMES)) { const sourceKey = path.join(process.cwd(), 'ssh-keys', keyName); From 36c59e6cfdfcfcd606d917e11496fdc01547aeda Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Thu, 12 Dec 2024 23:36:41 +0100 Subject: [PATCH 24/34] Fix typo in test script --- src/test/playwright/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/playwright/package.json b/src/test/playwright/package.json index 0624528ed7e7..5e37ba3caba9 100644 --- a/src/test/playwright/package.json +++ b/src/test/playwright/package.json @@ -17,7 +17,7 @@ "scripts": { "playwright:test": "npm-run-all --serial --continue-on-error playwright:test:parallel playwright:test:sequential merge-reports", "playwright:test:parallel": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-parallel.xml playwright test e2e --project=fast-tests --project=slow-tests", - "playwright:test:sequential": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-sequential.xml Dplaywright test e2e --project=sequential-tests --workers 1", + "playwright:test:sequential": "cross-env PLAYWRIGHT_JUNIT_OUTPUT_NAME=./test-reports/results-sequential.xml playwright test e2e --project=sequential-tests --workers 1", "playwright:open": "playwright test e2e --ui", "playwright:setup": "npx playwright install --with-deps chromium && playwright test init", "playwright:setup-local": "npx playwright install --with-deps chromium", From 25279b4bc271a00615bd63fbdd496e28e1821f20 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sat, 14 Dec 2024 01:19:34 +0100 Subject: [PATCH 25/34] Improve ssh server url regex --- .../programming/ProgrammingExerciseParticipation.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index bebe9a9a22f1..27945edfed8d 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -281,7 +281,7 @@ async function makeGitExerciseSubmission( repoUrl = repoUrl.replace('localhost', 'artemis-app'); } if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.ssh) { - repoUrl = repoUrl.replace(/ls1Agent.*\.ase\.cit\.tum\.de/, 'artemis-app'); + repoUrl = repoUrl.replace(/(?<=@).+(?=:)/, 'artemis-app'); } if (cloneMethod == GitCloneMethod.https) { repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); From 2da66d40afdbfd2ae598847feb3f35ddccb92236 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sat, 14 Dec 2024 03:33:23 +0100 Subject: [PATCH 26/34] Small code improvements --- .../programming/ProgrammingExerciseParticipation.spec.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 27945edfed8d..1b7f6511c465 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -277,15 +277,11 @@ async function makeGitExerciseSubmission( await programmingExerciseOverview.openCloneMenu(cloneMethod); } let repoUrl = await programmingExerciseOverview.copyCloneUrl(); - if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.https) { - repoUrl = repoUrl.replace('localhost', 'artemis-app'); - } - if (process.env.CI === 'true' && cloneMethod == GitCloneMethod.ssh) { + if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.ssh)) { repoUrl = repoUrl.replace(/(?<=@).+(?=:)/, 'artemis-app'); } if (cloneMethod == GitCloneMethod.https) { repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); - repoUrl = repoUrl.replace(`:**********`, ``); } console.log(`Cloning repository from ${repoUrl}`); const urlParts = repoUrl.split('/'); From 891bf7bcb3271655c90767f97d346345f91e2c12 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sat, 14 Dec 2024 13:14:18 +0100 Subject: [PATCH 27/34] Add ED25519 test --- .../ProgrammingExerciseParticipation.spec.ts | 30 ++++++++++--------- src/test/playwright/init/global-setup.ts | 4 +-- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 1b7f6511c465..bc575cd35551 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -98,20 +98,22 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = exercise = await exerciseAPIRequests.createProgrammingExercise({ course, programmingLanguage: ProgrammingLanguage.JAVA }); }); - test('Makes a git submission using SSH with RSA key', async ({ page, programmingExerciseOverview }) => { - await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); - await makeGitExerciseSubmission( - page, - programmingExerciseOverview, - course, - exercise, - studentOne, - javaAllSuccessfulSubmission, - 'Solution', - GitCloneMethod.ssh, - SshEncryptionAlgorithm.rsa, - ); - }); + for (const sshAlgorithm of [SshEncryptionAlgorithm.rsa, SshEncryptionAlgorithm.ed25519]) { + test(`Makes a git submission using SSH with ${sshAlgorithm} key`, async ({ page, programmingExerciseOverview }) => { + await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); + await makeGitExerciseSubmission( + page, + programmingExerciseOverview, + course, + exercise, + studentOne, + javaAllSuccessfulSubmission, + 'Solution', + GitCloneMethod.ssh, + sshAlgorithm, + ); + }); + } test.afterEach('Delete SSH key', async ({ accountManagementAPIRequests }) => { await accountManagementAPIRequests.deleteSshPublicKey(); diff --git a/src/test/playwright/init/global-setup.ts b/src/test/playwright/init/global-setup.ts index c3676c21b14f..59043b161b12 100644 --- a/src/test/playwright/init/global-setup.ts +++ b/src/test/playwright/init/global-setup.ts @@ -3,8 +3,8 @@ import path from 'path'; import os from 'os'; export enum SshEncryptionAlgorithm { - rsa, - ed25519, + rsa = 'RSA', + ed25519 = ' ED25519', } export const SSH_KEY_NAMES = { From 3e686afe7ce787a189e7a820042b50b98fe97fc7 Mon Sep 17 00:00:00 2001 From: Murad Talibov <56686446+muradium@users.noreply.github.com> Date: Sat, 14 Dec 2024 13:53:53 +0100 Subject: [PATCH 28/34] Update src/test/playwright/init/global-setup.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/test/playwright/init/global-setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/playwright/init/global-setup.ts b/src/test/playwright/init/global-setup.ts index 59043b161b12..608c35b78acd 100644 --- a/src/test/playwright/init/global-setup.ts +++ b/src/test/playwright/init/global-setup.ts @@ -4,7 +4,7 @@ import os from 'os'; export enum SshEncryptionAlgorithm { rsa = 'RSA', - ed25519 = ' ED25519', + ed25519 = 'ED25519', } export const SSH_KEY_NAMES = { From 3fba5ac89e3febc10cf291de07749d95690f423f Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sat, 14 Dec 2024 13:56:50 +0100 Subject: [PATCH 29/34] Error handling on global-setup --- src/test/playwright/init/global-setup.ts | 44 +++++++++++++----------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/test/playwright/init/global-setup.ts b/src/test/playwright/init/global-setup.ts index 608c35b78acd..78ab9b7f30d3 100644 --- a/src/test/playwright/init/global-setup.ts +++ b/src/test/playwright/init/global-setup.ts @@ -20,27 +20,31 @@ async function globalSetup() { fs.mkdirSync(sshDir, { recursive: true }); } - for (const keyName of Object.values(SSH_KEY_NAMES)) { - const sourceKey = path.join(process.cwd(), 'ssh-keys', keyName); - const sourceKeyPub = path.join(process.cwd(), 'ssh-keys', `${keyName}.pub`); - const destKey = path.join(sshDir, keyName); - const destKeyPub = path.join(sshDir, `${keyName}.pub`); - - if (!fs.existsSync(destKey)) { - fs.copyFileSync(sourceKey, destKey); - fs.chmodSync(destKey, 0o600); - console.log(`Private SSH key ${keyName} copied.`); - } else { - console.log(`Private SSH key ${keyName} already exists, skipping.`); - } - - if (!fs.existsSync(destKeyPub)) { - fs.copyFileSync(sourceKeyPub, destKeyPub); - fs.chmodSync(destKeyPub, 0o644); - console.log(`Public SSH key ${keyName} copied.`); - } else { - console.log(`Public SSH key ${keyName} already exists, skipping.`); + try { + for (const keyName of Object.values(SSH_KEY_NAMES)) { + const sourceKey = path.join(process.cwd(), 'ssh-keys', keyName); + const sourceKeyPub = path.join(process.cwd(), 'ssh-keys', `${keyName}.pub`); + const destKey = path.join(sshDir, keyName); + const destKeyPub = path.join(sshDir, `${keyName}.pub`); + + if (!fs.existsSync(destKey)) { + fs.copyFileSync(sourceKey, destKey); + fs.chmodSync(destKey, 0o600); + console.log(`Private SSH key ${keyName} copied.`); + } else { + console.log(`Private SSH key ${keyName} already exists, skipping.`); + } + + if (!fs.existsSync(destKeyPub)) { + fs.copyFileSync(sourceKeyPub, destKeyPub); + fs.chmodSync(destKeyPub, 0o644); + console.log(`Public SSH key ${keyName} copied.`); + } else { + console.log(`Public SSH key ${keyName} already exists, skipping.`); + } } + } catch (error) { + console.error('Error during SSH key setup:', error); } console.log('Global setup completed.'); From 13ad2275101856c7bc59dce12dc2fca1f7e7ccb3 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sat, 14 Dec 2024 22:21:53 +0100 Subject: [PATCH 30/34] Improve management of ssh keys by using them directly from Playwright directory --- .gitignore | 1 + .../ProgrammingExerciseParticipation.spec.ts | 6 +-- src/test/playwright/init/global-setup.ts | 41 +++---------------- .../exercises/programming/GitClient.ts | 20 ++++++++- 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 75cb003dda0c..fee3f7347d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -193,6 +193,7 @@ data-exports/ ###################### /src/test/playwright/test-reports/ /src/test/playwright/test-results/* +/src/test/playwright/ssh-keys/known_hosts ################################# # Files generated by prebuild.mjs diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index bc575cd35551..4d3bccbd8783 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -8,7 +8,7 @@ import pythonAllSuccessful from '../../../fixtures/exercise/programming/python/a import { BASE_API, ExerciseCommit, ExerciseMode, ProgrammingLanguage } from '../../../support/constants'; import { test } from '../../../support/fixtures'; import { BrowserContext, Page, expect } from '@playwright/test'; -import { gitClient } from '../../../support/pageobjects/exercises/programming/GitClient'; +import { SSH_KEYS_PATH, SSH_KEY_NAMES, SshEncryptionAlgorithm, gitClient } from '../../../support/pageobjects/exercises/programming/GitClient'; import * as fs from 'fs/promises'; import path from 'path'; import { SimpleGit } from 'simple-git'; @@ -20,7 +20,6 @@ import { UserCredentials, admin, instructor, studentFour, studentOne, studentTwo import { Team } from 'app/entities/team.model'; import { GitCloneMethod, ProgrammingExerciseOverviewPage } from '../../../support/pageobjects/exercises/programming/ProgrammingExerciseOverviewPage'; import { Participation } from 'app/entities/participation/participation.model'; -import { SSH_KEY_NAMES, SshEncryptionAlgorithm } from '../../../init/global-setup'; test.describe('Programming exercise participation', { tag: '@sequential' }, () => { let course: Course; @@ -301,8 +300,7 @@ async function makeGitExerciseSubmission( async function setupSSHCredentials(context: BrowserContext, sshAlgorithm: SshEncryptionAlgorithm) { console.log(`Setting up SSH credentials with key ${SSH_KEY_NAMES[sshAlgorithm]}`); const page = await context.newPage(); - const playwrightRoot = process.cwd(); - const sshKeyPath = path.join(playwrightRoot, 'ssh-keys', `${SSH_KEY_NAMES[sshAlgorithm]}.pub`); + const sshKeyPath = path.join(SSH_KEYS_PATH, `${SSH_KEY_NAMES[sshAlgorithm]}.pub`); const sshKey = await fs.readFile(sshKeyPath, 'utf8'); await page.goto('user-settings/ssh'); await page.getByTestId('addNewSshKeyButton').click(); diff --git a/src/test/playwright/init/global-setup.ts b/src/test/playwright/init/global-setup.ts index 78ab9b7f30d3..3782a7b565b9 100644 --- a/src/test/playwright/init/global-setup.ts +++ b/src/test/playwright/init/global-setup.ts @@ -1,47 +1,18 @@ import fs from 'fs'; import path from 'path'; -import os from 'os'; - -export enum SshEncryptionAlgorithm { - rsa = 'RSA', - ed25519 = 'ED25519', -} - -export const SSH_KEY_NAMES = { - [SshEncryptionAlgorithm.rsa]: 'artemis_playwright_rsa', - [SshEncryptionAlgorithm.ed25519]: 'artemis_playwright_ed25519', -}; +import { SSH_KEYS_PATH, SSH_KEY_NAMES } from '../support/pageobjects/exercises/programming/GitClient'; async function globalSetup() { console.log('Running global setup...'); - const sshDir = path.join(os.homedir(), '.ssh'); - if (!fs.existsSync(sshDir)) { - fs.mkdirSync(sshDir, { recursive: true }); - } - + // Set correct permissions to the SSH keys try { for (const keyName of Object.values(SSH_KEY_NAMES)) { - const sourceKey = path.join(process.cwd(), 'ssh-keys', keyName); - const sourceKeyPub = path.join(process.cwd(), 'ssh-keys', `${keyName}.pub`); - const destKey = path.join(sshDir, keyName); - const destKeyPub = path.join(sshDir, `${keyName}.pub`); - - if (!fs.existsSync(destKey)) { - fs.copyFileSync(sourceKey, destKey); - fs.chmodSync(destKey, 0o600); - console.log(`Private SSH key ${keyName} copied.`); - } else { - console.log(`Private SSH key ${keyName} already exists, skipping.`); - } + const privateKeyPath = path.join(SSH_KEYS_PATH, keyName); + const publicKeyPath = `${privateKeyPath}.pub`; - if (!fs.existsSync(destKeyPub)) { - fs.copyFileSync(sourceKeyPub, destKeyPub); - fs.chmodSync(destKeyPub, 0o644); - console.log(`Public SSH key ${keyName} copied.`); - } else { - console.log(`Public SSH key ${keyName} already exists, skipping.`); - } + fs.chmodSync(privateKeyPath, 0o600); + fs.chmodSync(publicKeyPath, 0o644); } } catch (error) { console.error('Error during SSH key setup:', error); diff --git a/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts b/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts index cfc87d5753f3..737c81ba8431 100644 --- a/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts +++ b/src/test/playwright/support/pageobjects/exercises/programming/GitClient.ts @@ -1,13 +1,17 @@ import { simpleGit } from 'simple-git'; import * as fs from 'fs'; +import path from 'path'; class GitClient { async cloneRepo(url: string, repoName: string, sshKeyName?: string) { const git = simpleGit(); const repoPath = `./${process.env.EXERCISE_REPO_DIRECTORY}/${repoName}`; - const gitSshCommand = sshKeyName ? `ssh -i ~/.ssh/${sshKeyName} -o StrictHostKeyChecking=no` : undefined; + let gitSshCommand; - if (gitSshCommand) { + if (sshKeyName) { + const privateKeyPath = path.join(SSH_KEYS_PATH, sshKeyName); + const knownHostsPath = path.join(SSH_KEYS_PATH, 'known_hosts'); + gitSshCommand = `ssh -i ${privateKeyPath} -o UserKnownHostsFile=${knownHostsPath} -o StrictHostKeyChecking=no`; git.env({ GIT_SSH_COMMAND: gitSshCommand }); } @@ -27,3 +31,15 @@ class GitClient { } export const gitClient = new GitClient(); + +export enum SshEncryptionAlgorithm { + rsa = 'RSA', + ed25519 = 'ED25519', +} + +export const SSH_KEY_NAMES = { + [SshEncryptionAlgorithm.rsa]: 'artemis_playwright_rsa', + [SshEncryptionAlgorithm.ed25519]: 'artemis_playwright_ed25519', +}; + +export const SSH_KEYS_PATH = path.join(process.cwd(), 'ssh-keys'); From ec012fa554abbba02ddecf1fb038b131701344bf Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sat, 14 Dec 2024 22:39:14 +0100 Subject: [PATCH 31/34] Minor code improvements --- .../ProgrammingExerciseParticipation.spec.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 4d3bccbd8783..09857e421a57 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -266,10 +266,10 @@ async function makeGitExerciseSubmission( submission: any, commitMessage: string, cloneMethod: GitCloneMethod = GitCloneMethod.https, - sshAlgorithm?: SshEncryptionAlgorithm, + sshAlgorithm: SshEncryptionAlgorithm = SshEncryptionAlgorithm.ed25519, ) { await programmingExerciseOverview.openCloneMenu(cloneMethod); - if (cloneMethod == GitCloneMethod.ssh && sshAlgorithm !== undefined) { + if (cloneMethod == GitCloneMethod.ssh) { await expect(programmingExerciseOverview.getCloneUrlButton()).toBeDisabled(); const sshKeyNotFoundAlert = page.locator('.alert', { hasText: 'To use ssh, you need to add an ssh key to your account' }); await expect(sshKeyNotFoundAlert).toBeVisible(); @@ -287,8 +287,12 @@ async function makeGitExerciseSubmission( console.log(`Cloning repository from ${repoUrl}`); const urlParts = repoUrl.split('/'); const repoName = urlParts[urlParts.length - 1]; - const sshKeyName = sshAlgorithm !== undefined ? SSH_KEY_NAMES[sshAlgorithm] : undefined; - const exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName, sshKeyName); + let exerciseRepo; + if (cloneMethod == GitCloneMethod.ssh) { + exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName, SSH_KEY_NAMES[sshAlgorithm]); + } else { + exerciseRepo = await gitClient.cloneRepo(repoUrl, repoName); + } console.log(`Cloned repository successfully. Pushing files...`); await pushGitSubmissionFiles(exerciseRepo, repoName, student, submission, commitMessage); await fs.rmdir(`./test-exercise-repos/${repoName}`, { recursive: true }); From 02f679816a80956d2c4a2d9147fa7872d093eb83 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sun, 15 Dec 2024 17:47:17 +0100 Subject: [PATCH 32/34] Avoid modifying server url on all git submission tests --- docker/playwright-E2E-tests-mysql-localci.yml | 2 ++ .../programming/ProgrammingExerciseParticipation.spec.ts | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docker/playwright-E2E-tests-mysql-localci.yml b/docker/playwright-E2E-tests-mysql-localci.yml index cf677b01fec6..13d2ba3b00ef 100644 --- a/docker/playwright-E2E-tests-mysql-localci.yml +++ b/docker/playwright-E2E-tests-mysql-localci.yml @@ -47,6 +47,8 @@ services: condition: service_healthy environment: PLAYWRIGHT_DB_TYPE: 'MySQL' + network_mode: service:artemis-app + networks: !reset [] networks: artemis: diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 09857e421a57..15fd2a784c8a 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -278,9 +278,6 @@ async function makeGitExerciseSubmission( await programmingExerciseOverview.openCloneMenu(cloneMethod); } let repoUrl = await programmingExerciseOverview.copyCloneUrl(); - if (process.env.CI === 'true' && (cloneMethod == GitCloneMethod.https || cloneMethod == GitCloneMethod.ssh)) { - repoUrl = repoUrl.replace(/(?<=@).+(?=:)/, 'artemis-app'); - } if (cloneMethod == GitCloneMethod.https) { repoUrl = repoUrl.replace(student.username!, `${student.username!}:${student.password!}`); } From 6c9eb4b0f55caac93603bd3d333a75775242acca Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sun, 15 Dec 2024 18:00:49 +0100 Subject: [PATCH 33/34] Adds programming submission test that uses token-based HTTPS --- .../ProgrammingExerciseParticipation.spec.ts | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 15fd2a784c8a..0a24fcfbca8e 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -97,25 +97,41 @@ test.describe('Programming exercise participation', { tag: '@sequential' }, () = exercise = await exerciseAPIRequests.createProgrammingExercise({ course, programmingLanguage: ProgrammingLanguage.JAVA }); }); - for (const sshAlgorithm of [SshEncryptionAlgorithm.rsa, SshEncryptionAlgorithm.ed25519]) { - test(`Makes a git submission using SSH with ${sshAlgorithm} key`, async ({ page, programmingExerciseOverview }) => { - await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); - await makeGitExerciseSubmission( - page, - programmingExerciseOverview, - course, - exercise, - studentOne, - javaAllSuccessfulSubmission, - 'Solution', - GitCloneMethod.ssh, - sshAlgorithm, - ); - }); - } + test('Makes a git submission through HTTPS using token', async ({ programmingExerciseOverview, page }) => { + await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); + await makeGitExerciseSubmission( + page, + programmingExerciseOverview, + course, + exercise, + studentOne, + javaAllSuccessfulSubmission, + 'Solution', + GitCloneMethod.httpsWithToken, + ); + }); - test.afterEach('Delete SSH key', async ({ accountManagementAPIRequests }) => { - await accountManagementAPIRequests.deleteSshPublicKey(); + test.describe('Programming exercise participation using SSH', () => { + for (const sshAlgorithm of [SshEncryptionAlgorithm.rsa, SshEncryptionAlgorithm.ed25519]) { + test(`Makes a git submission using SSH with ${sshAlgorithm} key`, async ({ page, programmingExerciseOverview }) => { + await programmingExerciseOverview.startParticipation(course.id!, exercise.id!, studentOne); + await makeGitExerciseSubmission( + page, + programmingExerciseOverview, + course, + exercise, + studentOne, + javaAllSuccessfulSubmission, + 'Solution', + GitCloneMethod.ssh, + sshAlgorithm, + ); + }); + } + + test.afterEach('Delete SSH key', async ({ accountManagementAPIRequests }) => { + await accountManagementAPIRequests.deleteSshPublicKey(); + }); }); }); From 346080a118538e936ba6aaf55db5884732a15646 Mon Sep 17 00:00:00 2001 From: Murad Talibov Date: Sun, 15 Dec 2024 18:06:40 +0100 Subject: [PATCH 34/34] Close ssh settings page after setting up ssh keys --- .../programming/ProgrammingExerciseParticipation.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts index 0a24fcfbca8e..415a17479a52 100644 --- a/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts +++ b/src/test/playwright/e2e/exercise/programming/ProgrammingExerciseParticipation.spec.ts @@ -325,6 +325,7 @@ async function setupSSHCredentials(context: BrowserContext, sshAlgorithm: SshEnc const responsePromise = page.waitForResponse(`${BASE_API}/ssh-settings/public-key`); await page.getByTestId('saveSshKeyButton').click(); await responsePromise; + await page.close(); } /**