Add cypress test for mentor invitation
This commit is contained in:
parent
f150751219
commit
3f02fd254a
|
|
@ -104,12 +104,13 @@ const noLearningMentors = computed(() =>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white px-4 py-2">
|
<div class="bg-white px-4 py-2">
|
||||||
<main>
|
<main data-cy="my-mentors-list">
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-for="invitation in invitations"
|
v-for="invitation in invitations"
|
||||||
:key="invitation.id"
|
:key="invitation.id"
|
||||||
class="flex flex-col justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:gap-16"
|
class="flex flex-col justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:gap-16"
|
||||||
|
:data-cy="`mentor-${invitation.email}`"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col md:flex-grow md:flex-row">
|
<div class="flex flex-col md:flex-grow md:flex-row">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
|
|
@ -178,6 +179,7 @@ const noLearningMentors = computed(() =>
|
||||||
<button
|
<button
|
||||||
:disabled="!validEmail"
|
:disabled="!validEmail"
|
||||||
class="btn-primary mt-8"
|
class="btn-primary mt-8"
|
||||||
|
data-cy="invite-mentor-button"
|
||||||
@click="inviteMentor()"
|
@click="inviteMentor()"
|
||||||
>
|
>
|
||||||
{{ $t("a.Einladung abschicken") }}
|
{{ $t("a.Einladung abschicken") }}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCSRFFetch } from "@/fetchHelpers";
|
import { itPost } from "@/fetchHelpers";
|
||||||
import { getLearningMentorUrl } from "@/utils/utils";
|
import { getLearningMentorUrl } from "@/utils/utils";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseId: string;
|
courseId: string;
|
||||||
invitationId: string;
|
invitationId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { data, error } = useCSRFFetch(
|
const loaded = ref<boolean>(false);
|
||||||
`/api/mentor/${props.courseId}/invitations/accept`,
|
const responseData = ref<any>(null);
|
||||||
{
|
const hasError = ref<boolean>(false);
|
||||||
onFetchError(ctx) {
|
const errorMessage = ref<string>("");
|
||||||
ctx.error = ctx.data;
|
|
||||||
return ctx;
|
onMounted(async () => {
|
||||||
},
|
const url = `/api/mentor/${props.courseId}/invitations/accept`;
|
||||||
}
|
itPost(url, {
|
||||||
)
|
|
||||||
.post({
|
|
||||||
invitation_id: props.invitationId,
|
invitation_id: props.invitationId,
|
||||||
})
|
})
|
||||||
.json();
|
.then((data) => {
|
||||||
|
responseData.value = data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
hasError.value = true;
|
||||||
|
if (error.toString().includes("404")) {
|
||||||
|
errorMessage.value = "Einladung bereits akzeptiert";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loaded.value = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -28,9 +39,9 @@ const { data, error } = useCSRFFetch(
|
||||||
<header class="mb-8 mt-12">
|
<header class="mb-8 mt-12">
|
||||||
<h1 class="mb-8">{{ $t("a.Einladung") }}</h1>
|
<h1 class="mb-8">{{ $t("a.Einladung") }}</h1>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main v-if="loaded">
|
||||||
<div class="bg-white p-6">
|
<div class="bg-white p-6">
|
||||||
<template v-if="error">
|
<template v-if="hasError">
|
||||||
{{
|
{{
|
||||||
$t(
|
$t(
|
||||||
"a.Die Einladung konnte nicht akzeptiert werden. Bitte melde dich beim Support."
|
"a.Die Einladung konnte nicht akzeptiert werden. Bitte melde dich beim Support."
|
||||||
|
|
@ -50,8 +61,8 @@ const { data, error } = useCSRFFetch(
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div v-if="error.message" class="my-4">
|
<div v-if="errorMessage" class="my-4">
|
||||||
{{ $t("a.Fehlermeldung") }}: {{ error.message }}
|
{{ $t("a.Fehlermeldung") }}: {{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|
@ -61,11 +72,16 @@ const { data, error } = useCSRFFetch(
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<template #name>
|
<template #name>
|
||||||
<b>{{ data.user.first_name }} {{ data.user.last_name }}</b>
|
<b>
|
||||||
|
{{ responseData.user.first_name }} {{ responseData.user.last_name }}
|
||||||
|
</b>
|
||||||
</template>
|
</template>
|
||||||
</i18next>
|
</i18next>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a class="underline" :href="getLearningMentorUrl(data.course_slug)">
|
<a
|
||||||
|
class="underline"
|
||||||
|
:href="getLearningMentorUrl(responseData.course_slug)"
|
||||||
|
>
|
||||||
{{ $t("a.Übersicht anschauen") }}
|
{{ $t("a.Übersicht anschauen") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ const signUpURL = computed(() => getSignUpURL(constructParams()));
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p class="mb-4 mt-12">{{ $t("a.Hast du schon ein Konto?") }}</p>
|
<p class="mb-4 mt-12">{{ $t("a.Hast du schon ein Konto?") }}</p>
|
||||||
<a :href="loginURL" class="btn-secondary">
|
<a :href="loginURL" class="btn-secondary" data-cy="login-button">
|
||||||
{{ $t("a.Anmelden") }}
|
{{ $t("a.Anmelden") }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,9 @@ export default defineConfig(({ mode }) => {
|
||||||
],
|
],
|
||||||
define: {},
|
define: {},
|
||||||
server: {
|
server: {
|
||||||
|
host: true,
|
||||||
port: 5173,
|
port: 5173,
|
||||||
hmr: { port: 5173 },
|
strictPort: true,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
const { defineConfig } = require("cypress");
|
import { defineConfig } from "cypress";
|
||||||
const { cloudPlugin } = require("cypress-cloud/plugin");
|
import { cloudPlugin } from "cypress-cloud/plugin";
|
||||||
|
import tasks from "./cypress/plugins/index.mjs";
|
||||||
|
|
||||||
module.exports = defineConfig({
|
export default defineConfig({
|
||||||
projectId: "RVEZS1",
|
projectId: "RVEZS1",
|
||||||
|
chromeWebSecurity: false,
|
||||||
watchForFileChanges: false,
|
watchForFileChanges: false,
|
||||||
video: true,
|
video: true,
|
||||||
viewportWidth: 1280,
|
viewportWidth: 1280,
|
||||||
|
|
@ -19,6 +21,7 @@ module.exports = defineConfig({
|
||||||
e2e: {
|
e2e: {
|
||||||
// experimentalSessionAndOrigin: true,
|
// experimentalSessionAndOrigin: true,
|
||||||
setupNodeEvents(on, config) {
|
setupNodeEvents(on, config) {
|
||||||
|
tasks(on, config);
|
||||||
return cloudPlugin(on, config);
|
return cloudPlugin(on, config);
|
||||||
},
|
},
|
||||||
baseUrl: "http://localhost:8001",
|
baseUrl: "http://localhost:8001",
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { login } from "../helpers";
|
||||||
|
import { TEST_STUDENT1_VV_USER_ID } from "../../consts";
|
||||||
|
|
||||||
|
describe("mentorInvitation.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Teilnehmer macht lädt Lernbegleitung ein; Lernbegleitung akzeptiert Einladung", () => {
|
||||||
|
login("student-vv@eiger-versicherungen.ch", "test");
|
||||||
|
cy.visit("/course/versicherungsvermittler-in/learn");
|
||||||
|
cy.get("[data-cy=navigation-learning-mentor-link]").click();
|
||||||
|
cy.get('[data-cy="lm-invite-mentor-button"]').click();
|
||||||
|
cy.get("#mentor-email").type("empty@example.com");
|
||||||
|
cy.get('[data-cy="invite-mentor-button"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="mentor-empty@example.com"]').should(
|
||||||
|
"contain",
|
||||||
|
"Die Einladung wurde noch nicht angenommen.",
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.task(
|
||||||
|
"runSql",
|
||||||
|
"select target_url from learning_mentor_mentorinvitation where email = 'empty@example.com'",
|
||||||
|
).then((res) => {
|
||||||
|
const invitationUrl = res.rows[0].target_url;
|
||||||
|
console.log(invitationUrl);
|
||||||
|
|
||||||
|
cy.visit("/");
|
||||||
|
cy.get('[data-cy="header-profile"]').click();
|
||||||
|
cy.get('[data-cy="logout-button"]').click();
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// try to accept invitation
|
||||||
|
cy.visit(invitationUrl);
|
||||||
|
cy.get('[data-cy="login-button"]').click();
|
||||||
|
cy.get("#username").type("empty@example.com");
|
||||||
|
cy.get("#password").type("test");
|
||||||
|
cy.get('[data-cy="login-button"]').click();
|
||||||
|
|
||||||
|
cy.get(".bg-white").should(
|
||||||
|
"contain",
|
||||||
|
"Du hast die Einladung von Viktor Vollgas erfolgreich akzeptiert.",
|
||||||
|
);
|
||||||
|
cy.contains("Übersicht anschauen").click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="lm-my-mentees"]').should(
|
||||||
|
"contain",
|
||||||
|
"Personen, die du begleitest",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="lm-my-mentees"]').should(
|
||||||
|
"contain",
|
||||||
|
"student-vv@eiger-versicherungen.ch",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
/// <reference types="cypress" />
|
|
||||||
// ***********************************************************
|
|
||||||
// This example plugins/index.js can be used to load plugins
|
|
||||||
//
|
|
||||||
// You can change the location of this file or turn off loading
|
|
||||||
// the plugins file with the 'pluginsFile' configuration option.
|
|
||||||
//
|
|
||||||
// You can read more here:
|
|
||||||
// https://on.cypress.io/plugins-guide
|
|
||||||
// ***********************************************************
|
|
||||||
|
|
||||||
// This function is called when a project is opened or re-opened (e.g. due to
|
|
||||||
// the project's config changing)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Cypress.PluginConfig}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
module.exports = (on, config) => {
|
|
||||||
// `on` is used to hook into various events Cypress emits
|
|
||||||
// `config` is the resolved Cypress config
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { runSql } from "./tasks.mjs";
|
||||||
|
|
||||||
|
export default (on, config) => {
|
||||||
|
on("task", {
|
||||||
|
runSql,
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import pg from "pg";
|
||||||
|
|
||||||
|
const cypressDatabaseUrl = process.env?.CYPRESS_DATABASE_URL || "postgres://postgres@localhost:5432/vbv_lernwelt_cypress";
|
||||||
|
|
||||||
|
if(!cypressDatabaseUrl) {
|
||||||
|
throw new Error(
|
||||||
|
"CYPRESS_DATABASE_URL must be set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runSql(sqlString) {
|
||||||
|
// I could not make postgres.js make work, so I use pg directly
|
||||||
|
const client = new pg.Client(cypressDatabaseUrl);
|
||||||
|
await client.connect();
|
||||||
|
const res = await client.query(sqlString);
|
||||||
|
await client.end();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -9,7 +9,8 @@
|
||||||
"prettier": "npm run prettier --prefix client"
|
"prettier": "npm run prettier --prefix client"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cypress": "^12.15.0",
|
"cypress": "^12.17.4",
|
||||||
"cypress-cloud": "^1.7.4"
|
"cypress-cloud": "^1.10.2",
|
||||||
|
"pg": "^8.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ from vbv_lernwelt.course.services import mark_course_completion
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus
|
from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus
|
||||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import LearningMentor, MentorInvitation
|
||||||
from vbv_lernwelt.learnpath.models import (
|
from vbv_lernwelt.learnpath.models import (
|
||||||
LearningContentAttendanceCourse,
|
LearningContentAttendanceCourse,
|
||||||
LearningContentFeedbackUK,
|
LearningContentFeedbackUK,
|
||||||
|
|
@ -145,6 +145,7 @@ def command(
|
||||||
CourseCompletionFeedback.objects.all().delete()
|
CourseCompletionFeedback.objects.all().delete()
|
||||||
|
|
||||||
LearningMentor.objects.all().delete()
|
LearningMentor.objects.all().delete()
|
||||||
|
MentorInvitation.objects.all().delete()
|
||||||
User.objects.all().update(organisation=Organisation.objects.first())
|
User.objects.all().update(organisation=Organisation.objects.first())
|
||||||
User.objects.all().update(language="de")
|
User.objects.all().update(language="de")
|
||||||
User.objects.all().update(additional_json_data={})
|
User.objects.all().update(additional_json_data={})
|
||||||
|
|
|
||||||
|
|
@ -34,5 +34,5 @@ class LearningMentorAdmin(admin.ModelAdmin):
|
||||||
@admin.register(MentorInvitation)
|
@admin.register(MentorInvitation)
|
||||||
class MentorInvitationAdmin(admin.ModelAdmin):
|
class MentorInvitationAdmin(admin.ModelAdmin):
|
||||||
list_display = ["id", "email", "participant", "created"]
|
list_display = ["id", "email", "participant", "created"]
|
||||||
readonly_fields = ["id", "created"]
|
readonly_fields = ["id", "created", "email", "participant"]
|
||||||
search_fields = ["email"]
|
search_fields = ["email"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.20 on 2024-07-18 08:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("learning_mentor", "0006_auto_20240319_1058"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="mentorinvitation",
|
||||||
|
name="target_url",
|
||||||
|
field=models.CharField(blank=True, default="", max_length=255),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -34,6 +34,7 @@ class MentorInvitation(TimeStampedModel):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
participant = models.ForeignKey(CourseSessionUser, on_delete=models.CASCADE)
|
participant = models.ForeignKey(CourseSessionUser, on_delete=models.CASCADE)
|
||||||
|
target_url = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.email} ({self.participant})"
|
return f"{self.email} ({self.participant})"
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,8 @@ def create_invitation(request, course_session_id: int):
|
||||||
|
|
||||||
invitation = serializer.save()
|
invitation = serializer.save()
|
||||||
target_url = f"/lernbegleitung/{course_session_id}/invitation/{invitation.id}"
|
target_url = f"/lernbegleitung/{course_session_id}/invitation/{invitation.id}"
|
||||||
|
invitation.target_url = target_url
|
||||||
|
invitation.save()
|
||||||
|
|
||||||
if course_session.course.configuration.is_uk:
|
if course_session.course.configuration.is_uk:
|
||||||
template = EmailTemplate.PRAXISBILDNER_INVITATION
|
template = EmailTemplate.PRAXISBILDNER_INVITATION
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue