From bc60e5b1e281c7fdf060c4036bc1d0f605215fd2 Mon Sep 17 00:00:00 2001 From: Chiara Marmo Date: Thu, 10 Oct 2024 11:03:40 +0200 Subject: [PATCH 1/4] Add rich_course_deployed fixture --- conftest.py | 23 ++++++++++++++++ travo/tests/test_course.py | 56 ++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/conftest.py b/conftest.py index 1e1e93d..a214797 100644 --- a/conftest.py +++ b/conftest.py @@ -211,6 +211,29 @@ def rich_course(gitlab: GitLabTest, test_run_id: str) -> Course: ) +@pytest.fixture +def rich_course_deployed(gitlab: GitLabTest, rich_course: Course) -> Iterator[Course]: + gitlab.login("instructor1", "aqwzsx(t3") + rich_course.forge.ensure_group( + path=rich_course.path, + name=rich_course.name, + visibility="public", + ) + + assert rich_course.session_name is not None + rich_course.forge.ensure_group( + path=rich_course.assignments_group_path, + name=rich_course.session_name, + visibility="public", + ) + gitlab.logout() + yield rich_course + + gitlab.login("instructor1", "aqwzsx(t3") + rich_course.forge.remove_group(rich_course.path) + gitlab.logout() + + @pytest.fixture def course_assignment_group(course: Course) -> Group: course.forge.ensure_group(path=course.path, name=course.name, visibility="public") diff --git a/travo/tests/test_course.py b/travo/tests/test_course.py index 901c9bf..5d6f0a2 100644 --- a/travo/tests/test_course.py +++ b/travo/tests/test_course.py @@ -4,6 +4,7 @@ import os from travo.console_scripts import Travo from travo.course import Course from travo.gitlab import GitLabTest +from travo.utils import working_directory @pytest.mark.parametrize("embed_option", [False, True]) @@ -77,10 +78,16 @@ def test_course_share_with(course): i18n.set("locale", "en") with pytest.raises(RuntimeError, match="No submission on GitLab"): - course.share_with(username="travo-test-etu", assignment_name="Assignment1") + course.share_with(username="student1", assignment_name="Assignment1") -def test_course_collect(gitlab, rich_course, to_be_teared_down, user_name, tmp_path): +def test_course_collect( + gitlab, rich_course_deployed, to_be_teared_down, user_name, tmp_path +): + rich_course = rich_course_deployed + assignments = rich_course.assignments + student_groups = rich_course.student_groups + gitlab.login("instructor1", "aqwzsx(t3") rich_course.forge.ensure_group( path=rich_course.path, @@ -93,8 +100,6 @@ def test_course_collect(gitlab, rich_course, to_be_teared_down, user_name, tmp_p name=rich_course.session_name, visibility="public", ) - assignments = rich_course.assignments - student_groups = rich_course.student_groups for group in student_groups: group_path = session_path + "/" + group @@ -116,29 +121,32 @@ def test_course_collect(gitlab, rich_course, to_be_teared_down, user_name, tmp_p gitlab.login("student1", "aqwzsx(t1") rich_course.fetch("Assignment1") rich_course.submit("Assignment1", student_group="Group1") + gitlab.logout() - rich_course.collect("Assignment1") - assert os.path.isdir(user_name) - - rich_course.collect( - "Assignment1", - student_group="Group1", - template="collect-{path}-Group1/{username}", - ) - assert os.path.isdir(f"collect-Assignment1-Group1/{user_name}") + gitlab.login("instructor1", "aqwzsx(t3") + with working_directory(tmp_path): + rich_course.collect("Assignment1") + assert os.path.isdir(os.path.join(tmp_path, "student1")) + + rich_course.collect( + "Assignment1", + student_group="Group1", + template="collect-{path}-Group1/{username}", + ) + assert os.path.isdir("collect-Assignment1-Group1/student1") - rich_course.collect( - "Assignment1", - student_group="Group2", - template="collect-{path}-Group2/{username}", - ) - assert not os.path.isdir(f"collect-Assignment1-Group2/{user_name}") + rich_course.collect( + "Assignment1", + student_group="Group2", + template="collect-{path}-Group2/{username}", + ) + assert not os.path.isdir("collect-Assignment1-Group2/student1") - rich_course.collect_in_submitted( - "Assignment1", - student_group="Group1", - ) - assert os.path.isdir(f"submitted/{user_name}") + rich_course.collect_in_submitted( + "Assignment1", + student_group="Group1", + ) + assert os.path.isdir("submitted/student1") def test_course_generate_and_release(rich_course): -- GitLab From fa4c642bc2e70e4c8c501789e952c4e2c9ce6f2d Mon Sep 17 00:00:00 2001 From: Chiara Marmo Date: Thu, 10 Oct 2024 12:28:20 +0200 Subject: [PATCH 2/4] Fix logins --- conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conftest.py b/conftest.py index a214797..010deeb 100644 --- a/conftest.py +++ b/conftest.py @@ -213,6 +213,7 @@ def rich_course(gitlab: GitLabTest, test_run_id: str) -> Course: @pytest.fixture def rich_course_deployed(gitlab: GitLabTest, rich_course: Course) -> Iterator[Course]: + gitlab.logout() gitlab.login("instructor1", "aqwzsx(t3") rich_course.forge.ensure_group( path=rich_course.path, -- GitLab From 7c226837331aefe3c15f3695a22def569193fbb5 Mon Sep 17 00:00:00 2001 From: Chiara Marmo Date: Thu, 10 Oct 2024 13:21:11 +0200 Subject: [PATCH 3/4] Add context manager logged_as, centralize test user definition in gitlab.py --- build_tools/create_basic_gitlab.py | 38 ++----- conftest.py | 13 ++- travo/gitlab.py | 123 ++++++++++++++++++++++- travo/tests/test_course.py | 153 +++++++++++++---------------- 4 files changed, 200 insertions(+), 127 deletions(-) diff --git a/build_tools/create_basic_gitlab.py b/build_tools/create_basic_gitlab.py index c24d261..bd3b364 100644 --- a/build_tools/create_basic_gitlab.py +++ b/build_tools/create_basic_gitlab.py @@ -6,6 +6,8 @@ from urllib.parse import urljoin from typing import Any, Dict, List, cast +from travo.gitlab import GitLabTest + def get_user(username: str) -> User: users = cast(List[User], gl.users.list(username=username)) @@ -58,35 +60,13 @@ gitlab_oauth_token = resp_data["access_token"] gl = gitlab.Gitlab(gitlab_url, oauth_token=gitlab_oauth_token) # create users -users_data = [ - { - "username": "student1", - "email": "travo@gmail.com", - "name": "Étudiant de test pour travo", - "password": "aqwzsx(t1", - "can_create_group": "True", - }, - { - "username": "student2", - "email": "student2@foo.bar", - "name": "Student 2", - "password": "aqwzsx(t2", - }, - { - "username": "instructor1", - "email": "instructor1@foo.bar", - "name": "Instructor 1", - "password": "aqwzsx(t3", - }, - { - "username": "instructor2", - "email": "instructor2@foo.bar", - "name": "Instructor 2", - "password": "aqwzsx(t4", - }, -] - -users = {user_data["username"]: create_user(user_data) for user_data in users_data} +users_data = GitLabTest.users + +users = { + user_data["username"]: create_user(user_data) + for user_data in users_data + if user_data["username"] != "root" +} user = users["student1"] diff --git a/conftest.py b/conftest.py index 010deeb..9c1a650 100644 --- a/conftest.py +++ b/conftest.py @@ -165,13 +165,12 @@ def to_be_teared_down() -> Iterator[Callable[[Resource], None]]: for resource in reversed(resources): forge = resource.gitlab assert isinstance(forge, GitLabTest) - forge.login("root", "dr0w554p!&ew=]gdS") - if isinstance(resource, Group): - forge.remove_group(resource.id) - else: - assert isinstance(resource, Project) - forge.remove_project(resource.id) - forge.logout() + with forge.logged_as("root"): + if isinstance(resource, Group): + forge.remove_group(resource.id) + else: + assert isinstance(resource, Project) + forge.remove_project(resource.id) @pytest.fixture diff --git a/travo/gitlab.py b/travo/gitlab.py index 2643e14..4ee2f18 100644 --- a/travo/gitlab.py +++ b/travo/gitlab.py @@ -1,4 +1,5 @@ import base64 +import contextlib from dataclasses import dataclass, field, InitVar, fields import enum import fnmatch @@ -17,6 +18,7 @@ import pathlib import typing import typing_utils # type: ignore from typing import ( + Iterator, Optional, List, Sequence, @@ -2658,8 +2660,40 @@ class GitLabTest(GitLab): """ base_url: str = "http://gitlab/" - username: str = "student1" - password: str = "aqwzsx(t1" + + # The following data should be shared with build_tools/create_basic_gitlab.py + users = [ + { + "username": "root", + "password": "dr0w554p!&ew=]gdS", + }, + { + "username": "student1", + "email": "travo@gmail.com", + "name": "Étudiant de test pour travo", + "password": "aqwzsx(t1", + "can_create_group": "True", + }, + { + "username": "student2", + "email": "student2@foo.bar", + "name": "Student 2", + "password": "aqwzsx(t2", + }, + { + "username": "instructor1", + "email": "instructor1@foo.bar", + "name": "Instructor 1", + "password": "aqwzsx(t3", + }, + { + "username": "instructor2", + "email": "instructor2@foo.bar", + "name": "Instructor 2", + "password": "aqwzsx(t4", + }, + ] + passwords = {user["username"]: user["password"] for user in users} def __init__(self, base_url: str = base_url) -> None: if "GITLAB_HOST" in os.environ and "GITLAB_80_TCP_PORT" in os.environ: @@ -2674,8 +2708,89 @@ class GitLabTest(GitLab): def login( self, - username: Optional[str] = username, - password: Optional[str] = password, + username: Optional[str] = None, + password: Optional[str] = None, anonymous_ok: bool = False, ) -> None: + """ + Ensure that this GitLabTest session is authenticated + + This behaves as GitLab.login, with two additional features: + + - If a username is given from the list of predefined users in + `self.users`, then the password is filled by default + automatically. + + - If no username is provided, and not logged in, then the + session is authenticated as "student1" (mimicking the usual + behavior of the current user typing the credentials + interactively). + """ + if username is None and self.token is None: + username = "student1" + if username is not None: + password = self.passwords.get(username) super().login(username=username, password=password, anonymous_ok=anonymous_ok) + self.log.info(f"LOGIN: {self.get_current_user().username} {username}") + assert ( + username is None + or anonymous_ok + or self.get_current_user().username == username + ) + + @contextlib.contextmanager + def logged_as( + self, + username: Optional[str] = None, + password: Optional[str] = None, + anonymous_ok: bool = False, + ) -> Iterator: + """ + Defines a context for this gitlab session with temporary + authentication as another user + + Example: + + >>> gitlab = getfixture('gitlab') + >>> gitlab.login('student1') + >>> gitlab.get_current_user().username + 'student1' + >>> with gitlab.logged_as('student2'): + ... gitlab.get_current_user().username + 'student2' + >>> gitlab.get_current_user().username + 'student1' + + >>> gitlab.logout() + >>> gitlab.get_current_user() + Traceback (most recent call last): + ... + requests.exceptions.HTTPError:... + >>> with gitlab.logged_as('student2'): + ... gitlab.get_current_user().username + 'student2' + >>> gitlab.get_current_user() + Traceback (most recent call last): + ... + requests.exceptions.HTTPError:... + + >>> gitlab.login('anonymous', anonymous_ok=True) + >>> gitlab.get_current_user().username + 'anonymous' + >>> with gitlab.logged_as('student2'): + ... gitlab.get_current_user().username + 'student2' + >>> gitlab.get_current_user().username # doctest: +SKIP + 'student1' + """ + save_token = self.token + try: + self.logout() + assert self._current_user is None + self.login(username=username, password=password, anonymous_ok=anonymous_ok) + yield self + finally: + self.logout() + if save_token is not None: + self.set_token(save_token) + # Maybe something needs to be done to restore anonymous login? diff --git a/travo/tests/test_course.py b/travo/tests/test_course.py index 5d6f0a2..a7d67d9 100644 --- a/travo/tests/test_course.py +++ b/travo/tests/test_course.py @@ -3,7 +3,6 @@ import pytest import os from travo.console_scripts import Travo from travo.course import Course -from travo.gitlab import GitLabTest from travo.utils import working_directory @@ -48,10 +47,10 @@ def test_group_submission_true(rich_course): ) -def test_check_course_parameters(): +def test_check_course_parameters(gitlab): i18n.set("locale", "en") course = Course( - forge=GitLabTest(), + forge=gitlab, path="Info111", name="Info 111 Programmation Impérative", student_dir="~/ProgImperative", @@ -87,69 +86,61 @@ def test_course_collect( rich_course = rich_course_deployed assignments = rich_course.assignments student_groups = rich_course.student_groups - - gitlab.login("instructor1", "aqwzsx(t3") - rich_course.forge.ensure_group( - path=rich_course.path, - name=rich_course.name, - visibility="public", - ) - session_path = rich_course.path + "/" + rich_course.session_path - rich_course.forge.ensure_group( - path=session_path, - name=rich_course.session_name, - visibility="public", - ) - - for group in student_groups: - group_path = session_path + "/" + group - group_name = group - rich_course.forge.ensure_group( - path=group_path, name=group_name, visibility="public" - ) - for assignment in assignments: - path = rich_course.assignments_group_path + "/" + assignment - project = rich_course.forge.ensure_project( - path=path, name=assignment, visibility="public" + # Deploy the course assignments ("Assignment1" and "Assignment2") + with gitlab.logged_as("instructor1"): + for group in student_groups: + group_path = rich_course.assignments_group_path + "/" + group + group_name = group + rich_course.forge.ensure_group( + path=group_path, name=group_name, visibility="public" ) - project.ensure_file("README.md", branch="master") - path = group_path + "/" + assignment - project.ensure_fork(path, assignment, visibility="public") - - gitlab.logout() - os.chdir(tmp_path) - gitlab.login("student1", "aqwzsx(t1") - rich_course.fetch("Assignment1") - rich_course.submit("Assignment1", student_group="Group1") - gitlab.logout() - - gitlab.login("instructor1", "aqwzsx(t3") - with working_directory(tmp_path): - rich_course.collect("Assignment1") - assert os.path.isdir(os.path.join(tmp_path, "student1")) - - rich_course.collect( - "Assignment1", - student_group="Group1", - template="collect-{path}-Group1/{username}", - ) - assert os.path.isdir("collect-Assignment1-Group1/student1") + for assignment in assignments: + path = rich_course.assignments_group_path + "/" + assignment + project = rich_course.forge.ensure_project( + path=path, name=assignment, visibility="public" + ) + project.ensure_file("README.md", branch="master") + path = group_path + "/" + assignment + project.ensure_fork( + path, assignment, visibility="public", initialized=True + ) + + assignment = rich_course.assignment("Assignment1", student_group="Group1") + for student_name in ["student1", "student2"]: + with gitlab.logged_as(student_name): + to_be_teared_down(assignment.ensure_submission_repo(initialized=True)) + + with gitlab.logged_as("instructor1"): + with working_directory(tmp_path): + rich_course.collect("Assignment1") + assert os.path.isdir(os.path.join(tmp_path, "student1")) + assert os.path.isdir(os.path.join(tmp_path, "student2")) + + rich_course.collect( + "Assignment1", + student_group="Group1", + template="collect-{path}-Group1/{username}", + ) + assert os.path.isdir("collect-Assignment1-Group1/student1") + assert os.path.isdir("collect-Assignment1-Group1/student2") - rich_course.collect( - "Assignment1", - student_group="Group2", - template="collect-{path}-Group2/{username}", - ) - assert not os.path.isdir("collect-Assignment1-Group2/student1") + rich_course.collect( + "Assignment1", + student_group="Group2", + template="collect-{path}-Group2/{username}", + ) + assert not os.path.isdir("collect-Assignment1-Group2/student1") - rich_course.collect_in_submitted( - "Assignment1", - student_group="Group1", - ) - assert os.path.isdir("submitted/student1") + rich_course.collect_in_submitted( + "Assignment1", + student_group="Group1", + ) + assert os.path.isdir("submitted/student1") + assert os.path.isdir("submitted/student2") -def test_course_generate_and_release(rich_course): +def test_course_generate_and_release(gitlab, rich_course_deployed): + rich_course = rich_course_deployed rich_course.group_submissions = True source_dir = "./source" release_dir = "./release" @@ -166,31 +157,19 @@ def test_course_generate_and_release(rich_course): rich_course.source_dir = source_dir rich_course.gitlab_ci_yml = "my gitlab-ci.yml file" - # generate the assignment before release - rich_course.generate_assignment(assignment_name) - - assert os.path.isdir(release_dir) - assert os.path.isdir(os.path.join(release_dir, assignment_name)) - assert os.path.isdir(os.path.join(release_dir, assignment_name, ".git")) - assert os.path.isfile(os.path.join(release_dir, assignment_name, ".gitlab-ci.yml")) - assert os.path.isfile(os.path.join(release_dir, assignment_name, ".gitignore")) - - rich_course.forge.ensure_group( - path=rich_course.path, - name=rich_course.name, - visibility="public", - ) - - rich_course.forge.ensure_group( - path=rich_course.assignments_group_path, - name=rich_course.session_name, - visibility="public", - ) + with gitlab.logged_as("instructor1"): + # generate the assignment before release + rich_course.generate_assignment(assignment_name) - rich_course.release( - assignment_name, - path=os.path.join(release_dir, assignment_name), - ) + assert os.path.isdir(release_dir) + assert os.path.isdir(os.path.join(release_dir, assignment_name)) + assert os.path.isdir(os.path.join(release_dir, assignment_name, ".git")) + assert os.path.isfile( + os.path.join(release_dir, assignment_name, ".gitlab-ci.yml") + ) + assert os.path.isfile(os.path.join(release_dir, assignment_name, ".gitignore")) - # Clean after test - rich_course.forge.remove_group(rich_course.assignments_group_path, force=True) + rich_course.release( + assignment_name, + path=os.path.join(release_dir, assignment_name), + ) -- GitLab From e4888d81b0a113f66488cb272d76b1d3e8e8c600 Mon Sep 17 00:00:00 2001 From: Chiara Marmo Date: Fri, 11 Oct 2024 17:45:27 +0200 Subject: [PATCH 4/4] Add forgotten context. --- conftest.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/conftest.py b/conftest.py index 9c1a650..47c7148 100644 --- a/conftest.py +++ b/conftest.py @@ -212,26 +212,24 @@ def rich_course(gitlab: GitLabTest, test_run_id: str) -> Course: @pytest.fixture def rich_course_deployed(gitlab: GitLabTest, rich_course: Course) -> Iterator[Course]: - gitlab.logout() - gitlab.login("instructor1", "aqwzsx(t3") - rich_course.forge.ensure_group( - path=rich_course.path, - name=rich_course.name, - visibility="public", - ) + with gitlab.logged_as("instructor1"): + rich_course.forge.ensure_group( + path=rich_course.path, + name=rich_course.name, + visibility="public", + ) + + assert rich_course.session_name is not None + rich_course.forge.ensure_group( + path=rich_course.assignments_group_path, + name=rich_course.session_name, + visibility="public", + ) - assert rich_course.session_name is not None - rich_course.forge.ensure_group( - path=rich_course.assignments_group_path, - name=rich_course.session_name, - visibility="public", - ) - gitlab.logout() yield rich_course - gitlab.login("instructor1", "aqwzsx(t3") - rich_course.forge.remove_group(rich_course.path) - gitlab.logout() + with gitlab.logged_as("instructor1"): + rich_course.forge.remove_group(rich_course.path) @pytest.fixture -- GitLab