From ddd34015372f9e3ba8b869d3dd7f1018611e27c1 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Thu, 5 Dec 2024 17:09:23 +0100 Subject: [PATCH 01/20] feat(client): add members related endpoints --- pygitguardian/client.py | 120 +++++++++++++++++ pygitguardian/models.py | 166 +++++++++++++++++++++++- tests/cassettes/test_delete_member.yaml | 54 ++++++++ tests/cassettes/test_list_members.yaml | 64 +++++++++ tests/cassettes/test_update_member.yaml | 58 +++++++++ tests/test_client.py | 73 +++++++++++ 6 files changed, 532 insertions(+), 3 deletions(-) create mode 100644 tests/cassettes/test_delete_member.yaml create mode 100644 tests/cassettes/test_list_members.yaml create mode 100644 tests/cassettes/test_update_member.yaml diff --git a/pygitguardian/client.py b/pygitguardian/client.py index efddb25f..c3d7224e 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -26,6 +26,8 @@ ) from .models import ( APITokensResponse, + CursorPaginatedResponse, + DeleteMember, Detail, Document, DocumentSchema, @@ -34,6 +36,8 @@ HoneytokenWithContextResponse, JWTResponse, JWTService, + Member, + MembersParameters, MultiScanResult, QuotaResponse, RemediationMessages, @@ -41,6 +45,7 @@ SecretIncident, SecretScanPreferences, ServerMetadata, + UpdateMember, ) from .sca_models import ( ComputeSCAFilesResult, @@ -335,6 +340,40 @@ def post( **kwargs, ) + def patch( + self, + endpoint: str, + data: Union[Dict[str, Any], List[Dict[str, Any]], None] = None, + version: str = DEFAULT_API_VERSION, + extra_headers: Optional[Dict[str, str]] = None, + **kwargs: Any, + ) -> Response: + return self.request( + "patch", + endpoint=endpoint, + json=data, + version=version, + extra_headers=extra_headers, + **kwargs, + ) + + def delete( + self, + endpoint: str, + data: Union[Dict[str, Any], List[Dict[str, Any]], None] = None, + version: str = DEFAULT_API_VERSION, + extra_headers: Optional[Dict[str, str]] = None, + **kwargs: Any, + ) -> Response: + return self.request( + "delete", + endpoint=endpoint, + json=data, + version=version, + extra_headers=extra_headers, + **kwargs, + ) + def health_check(self) -> HealthCheckResponse: """ health_check handles the /health endpoint of the API @@ -859,3 +898,84 @@ def scan_diff( result = load_detail(response) result.status_code = response.status_code return result + + def list_members( + self, + query_parameters: Optional[MembersParameters] = None, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, CursorPaginatedResponse[Member]]: + + response = self.get( + endpoint="members", + data=query_parameters.to_dict() if query_parameters else {}, + extra_headers=extra_headers, + ) + + obj: Union[Detail, CursorPaginatedResponse[Member]] + if is_ok(response): + obj = CursorPaginatedResponse[Member].from_response(response, Member) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def get_member( + self, + member_id: int, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, Member]: + response = self.get( + endpoint=f"members/{member_id}", + extra_headers=extra_headers, + ) + obj: Union[Detail, Member] + if is_ok(response): + obj = Member.from_dict(response.json()) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def update_member( + self, + payload: UpdateMember, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, Member]: + + member_id = payload.id + data = UpdateMember.to_dict(payload) + del data["id"] + + response = self.patch( + f"members/{member_id}", data=data, extra_headers=extra_headers + ) + obj: Union[Detail, Member] + if is_ok(response): + obj = Member.from_dict(response.json()) + print("Member : ", obj) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def delete_member( + self, + member: DeleteMember, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, int]: + member_id = member.id + data = member.to_dict() + del data["id"] + + response = self.delete( + f"members/{member_id}", params=data, extra_headers=extra_headers + ) + + # We bypass `is_ok` because the response content type is none + if response.status_code == 204: + return 204 + + return load_detail(response) diff --git a/pygitguardian/models.py b/pygitguardian/models.py index ac84a258..002379ee 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -4,7 +4,19 @@ from dataclasses import dataclass, field from datetime import date, datetime from enum import Enum -from typing import Any, ClassVar, Dict, List, Literal, Optional, Type, cast +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Dict, + Generic, + List, + Literal, + Optional, + Type, + TypeVar, + cast, +) from uuid import UUID import marshmallow_dataclass @@ -13,6 +25,7 @@ Schema, ValidationError, fields, + post_dump, post_load, pre_load, validate, @@ -28,6 +41,10 @@ ) +if TYPE_CHECKING: + import requests + + class ToDictMixin: """ Provides a type-safe `to_dict()` method for classes using Marshmallow @@ -54,8 +71,8 @@ class FromDictMixin: SCHEMA: ClassVar[Schema] @classmethod - def from_dict(cls, dct: Dict[str, Any]) -> Self: - return cast(Self, cls.SCHEMA.load(dct)) + def from_dict(cls, dct: Dict[str, Any], many: Optional[bool] = None) -> Self: + return cast(Self, cls.SCHEMA.load(dct, many=many)) class BaseSchema(Schema): @@ -1077,3 +1094,146 @@ def __repr__(self) -> str: marshmallow_dataclass.class_schema(SecretIncident, base_schema=BaseSchema), ) SecretIncident.SCHEMA = SecretIncidentSchema() + + +class AccessLevel(str, Enum): + OWNER = "owner" + MANAGER = "manager" + MEMBER = "member" + RESTRICTED = "restricted" + + +class PaginationParameter(Base, FromDictMixin): + """Pagination mixin used for endpoints that support pagination.""" + + cursor: str = "" + per_page: int = 20 + + +class SearchParameter(Base, FromDictMixin): + search: Optional[str] = None + + +PaginatedData = TypeVar("PaginatedData", bound=FromDictMixin) + + +@dataclass +class CursorPaginatedResponse(Generic[PaginatedData]): + status_code: int + data: list[PaginatedData] + prev: Optional[str] = None + next: Optional[str] = None + + @classmethod + def from_response( + cls, response: "requests.Response", data_type: Type[PaginatedData] + ) -> "CursorPaginatedResponse[PaginatedData]": + data = cast( + list[PaginatedData], data_type.from_dict(response.json(), many=True) + ) + paginated_response = cls(status_code=response.status_code, data=data) + + if previous_page := response.links.get("prev"): + paginated_response.prev = previous_page["url"] + if next_page := response.links.get("next"): + paginated_response.prev = next_page["url"] + + return paginated_response + + +@dataclass +class MembersParameters(PaginationParameter, SearchParameter, Base, FromDictMixin): + """ + Members query parameters + """ + + access_level: Optional[AccessLevel] = None + active: Optional[bool] = None + ordering: Optional[ + Literal["id", "-id", "created_at", "-created_at", "last_login", "-last_login"] + ] = None + + +MembersParametersSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(MembersParameters, base_schema=BaseSchema), +) +MembersParameters.SCHEMA = MembersParametersSchema() + + +@dataclass +class Member(Base, FromDictMixin): + """ + Member represents a user in a GitGuardian account. + """ + + id: int + access_level: AccessLevel + email: str + name: str + created_at: datetime + last_login: Optional[datetime] + active: bool + + +class MemberSchema(BaseSchema): + id = fields.Int(required=True) + access_level = fields.Enum(AccessLevel, by_value=True, required=True) + email = fields.Str(required=True) + name = fields.Str(required=True) + created_at = fields.AwareDateTime(required=True) + last_login = fields.AwareDateTime(allow_none=True) + active = fields.Bool(required=True) + + @post_load + def return_member( + self, + data: list[dict[str, Any]] | dict[str, Any], + **kwargs: dict[str, Any], + ): + data = cast(dict[str, Any], data) + return Member(**data) + + +Member.SCHEMA = MemberSchema() + + +class UpdateMemberSchema(BaseSchema): + id = fields.Int(required=True) + access_level = fields.Enum(AccessLevel, by_value=True, allow_none=True) + active = fields.Bool(allow_none=True) + + @post_dump + def access_level_value( + self, data: dict[str, Any], **kwargs: dict[str, Any] + ) -> dict[str, Any]: + if "access_level" in data: + data["access_level"] = AccessLevel(data["access_level"]).value + return data + + +@dataclass +class UpdateMember(Base, FromDictMixin): + """ + UpdateMember represnets the payload to update a member + """ + + id: int + access_level: Optional[AccessLevel] = None + active: Optional[bool] = None + + +UpdateMember.SCHEMA = UpdateMemberSchema() + + +@dataclass +class DeleteMember(Base, FromDictMixin): + id: int + send_email: Optional[bool] = None + + +DeleteMemberSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(DeleteMember, base_schema=BaseSchema), +) +DeleteMember.SCHEMA = DeleteMemberSchema() diff --git a/tests/cassettes/test_delete_member.yaml b/tests/cassettes/test_delete_member.yaml new file mode 100644 index 00000000..81b3d8ab --- /dev/null +++ b/tests/cassettes/test_delete_member.yaml @@ -0,0 +1,54 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: DELETE + uri: https://api.gitguardian.com/v1/members/11 + response: + body: + string: '' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '0' + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 16:02:49 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 6471d63d5fe47af9711f900e0911ca4b + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/cassettes/test_list_members.yaml b/tests/cassettes/test_list_members.yaml new file mode 100644 index 00000000..0306ff33 --- /dev/null +++ b/tests/cassettes/test_list_members.yaml @@ -0,0 +1,64 @@ +interactions: + - request: + body: "0\r\n\r\n" + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Type: + - application/x-www-form-urlencoded + Transfer-Encoding: + - chunked + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/members + response: + body: + string: + '[{"id":6,"role":"manager","access_level":"manager","email":"toto@gg.com","name":"toto + tata","created_at":"2019-07-15T12:14:14.245000Z","last_login":"2024-12-03T09:29:43.181169Z","active":true}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '193' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 14:27:23 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 218dc7bba82abc6b71f3df240dfb1536 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_update_member.yaml b/tests/cassettes/test_update_member.yaml new file mode 100644 index 00000000..00b8466f --- /dev/null +++ b/tests/cassettes/test_update_member.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: '{"access_level": "member", "active": false}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '43' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: PATCH + uri: https://api.gitguardian.com/v1/members/10 + response: + body: + string: '{"id":10,"role":"member","access_level":"member","email":"owl@gg.com","name":"owl","created_at":"2024-12-05T15:02:15.901868Z","last_login":null,"active":false}' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '232' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 15:15:23 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 10079088f9a934a979e4992ca9ad6730 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_client.py b/tests/test_client.py index d21ff672..f59efac9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,15 +23,20 @@ MULTI_DOCUMENT_LIMIT, ) from pygitguardian.models import ( + AccessLevel, APITokensResponse, + CursorPaginatedResponse, + DeleteMember, Detail, HoneytokenResponse, HoneytokenWithContextResponse, JWTResponse, JWTService, + Member, MultiScanResult, QuotaResponse, ScanResult, + UpdateMember, ) from pygitguardian.sca_models import ( ComputeSCAFilesResult, @@ -1391,3 +1396,71 @@ def test_read_metadata_remediation_message(client: GGClient): assert client.remediation_messages.pre_commit == messages["pre_commit"] assert client.remediation_messages.pre_push == messages["pre_push"] assert client.remediation_messages.pre_receive == messages["pre_receive"] + + +LIST_MEMBERS_RESPONSE = [ + { + "id": 3251, + "name": "Owl", + "email": "john.smith@example.org", + "role": "owner", + "access_level": "owner", + "active": True, + "created_at": "2022-04-20T11:07:24.000Z", + "last_login": "2022-04-20T11:07:24.000Z", + }, + { + "id": 3252, + "name": "Owl", + "email": "john.smith@example.org", + "role": "owner", + "access_level": "owner", + "active": True, + "created_at": "2022-04-20T11:07:24.000Z", + "last_login": "2022-04-20T11:07:24.000Z", + }, +] + + +@my_vcr.use_cassette("test_list_members.yaml", ignore_localhost=False) +def test_list_members(client: GGClient): + """ + GIVEN a client + WHEN calling /members endpoint + THEN it returns a paginated list of members + """ + + result = client.list_members() + + assert isinstance(result, CursorPaginatedResponse), result.content + + +@my_vcr.use_cassette("test_update_member.yaml", ignore_localhost=False) +def test_update_member(client: GGClient): + """ + GIVEN a client + WHEN calling PATCH /members/{id} endpoint with a payload + THEN it returns the updated member + """ + + result = client.update_member( + UpdateMember(id=10, access_level=AccessLevel.MEMBER, active=False) + ) + + assert isinstance(result, Member), result.content + + assert not result.active + assert result.access_level == AccessLevel.MEMBER + + +@my_vcr.use_cassette("test_delete_member.yaml", ignore_localhost=False) +def test_delete_member(client: GGClient): + """ + GIVEN a client + WHEN calling DELETE /members/{id} endpoint + THEN the member is deleted + """ + + result = client.delete_member(DeleteMember(id=11)) + + assert result == 204, result.content From d322eaaa619a3a7e22e59887c75f135dde0c455e Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Thu, 5 Dec 2024 18:01:56 +0100 Subject: [PATCH 02/20] feat(client): add teams related endpoints --- pygitguardian/client.py | 238 +++++++++++++++++- pygitguardian/config.py | 2 +- pygitguardian/models.py | 238 ++++++++++++++++++ tests/cassettes/test_create_team.yaml | 58 +++++ .../test_create_team_invitation.yaml | 58 +++++ tests/cassettes/test_create_team_member.yaml | 58 +++++ tests/cassettes/test_delete_team.yaml | 54 ++++ .../test_delete_team_invitation.yaml | 54 ++++ tests/cassettes/test_delete_team_member.yaml | 54 ++++ tests/cassettes/test_get_team.yaml | 54 ++++ .../cassettes/test_list_team_invitations.yaml | 58 +++++ tests/cassettes/test_list_team_members.yaml | 58 +++++ tests/cassettes/test_list_teams.yaml | 60 +++++ tests/cassettes/test_update_team.yaml | 58 +++++ tests/test_client.py | 171 +++++++++++++ 15 files changed, 1270 insertions(+), 3 deletions(-) create mode 100644 tests/cassettes/test_create_team.yaml create mode 100644 tests/cassettes/test_create_team_invitation.yaml create mode 100644 tests/cassettes/test_create_team_member.yaml create mode 100644 tests/cassettes/test_delete_team.yaml create mode 100644 tests/cassettes/test_delete_team_invitation.yaml create mode 100644 tests/cassettes/test_delete_team_member.yaml create mode 100644 tests/cassettes/test_get_team.yaml create mode 100644 tests/cassettes/test_list_team_invitations.yaml create mode 100644 tests/cassettes/test_list_team_members.yaml create mode 100644 tests/cassettes/test_list_teams.yaml create mode 100644 tests/cassettes/test_update_team.yaml diff --git a/pygitguardian/client.py b/pygitguardian/client.py index c3d7224e..941aae94 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -26,6 +26,9 @@ ) from .models import ( APITokensResponse, + CreateTeam, + CreateTeamInvitation, + CreateTeamMember, CursorPaginatedResponse, DeleteMember, Detail, @@ -45,7 +48,14 @@ SecretIncident, SecretScanPreferences, ServerMetadata, + Team, + TeamInvitation, + TeamInvitationParameter, + TeamMember, + TeamMemberParameter, + TeamsParameter, UpdateMember, + UpdateTeam, ) from .sca_models import ( ComputeSCAFilesResult, @@ -120,6 +130,14 @@ def is_create_ok(resp: Response) -> bool: ) +def is_delete_ok(resp: Response) -> bool: + """ + is_delete_ok returns True if the API returns code 204 + and the content type is JSON. + """ + return resp.status_code == codes.no_content + + def _create_tar(root_path: Path, filenames: List[str]) -> bytes: """ :param root_path: the root_path from which the tar is created @@ -975,7 +993,223 @@ def delete_member( ) # We bypass `is_ok` because the response content type is none - if response.status_code == 204: - return 204 + if is_delete_ok(response): + return response.status_code + + return load_detail(response) + + def list_teams( + self, + parameters: Optional[TeamsParameter] = None, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, CursorPaginatedResponse[Team]]: + response = self.get( + endpoint="teams", + data=parameters.to_dict() if parameters else {}, + extra_headers=extra_headers, + ) + + obj: Union[Detail, CursorPaginatedResponse[Team]] + if is_ok(response): + obj = CursorPaginatedResponse[Team].from_response(response, Team) + else: + obj = load_detail(response) + + obj.status_code + return obj + + def get_team( + self, + team_id: int, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, Team]: + response = self.get( + endpoint=f"teams/{team_id}", + extra_headers=extra_headers, + ) + + obj: Union[Detail, Team] + if is_ok(response): + obj = Team.from_dict(response.json()) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def create_team( + self, + team: CreateTeam, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, Team]: + response = self.post( + endpoint="teams", data=CreateTeam.to_dict(team), extra_headers=extra_headers + ) + + obj: Union[Detail, Team] + if is_create_ok(response): + obj = Team.from_dict(response.json()) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def update_team( + self, + team: UpdateTeam, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, Team]: + team_id = team.id + data = UpdateTeam.to_dict(team) + del data["id"] + + response = self.patch( + endpoint=f"teams/{team_id}", + data=data, + extra_headers=extra_headers, + ) + + obj: Union[Detail, Team] + if is_ok(response): + obj = Team.from_dict(response.json()) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def delete_team( + self, + team_id: int, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, int]: + response = self.delete( + endpoint=f"teams/{team_id}", + extra_headers=extra_headers, + ) + + if is_delete_ok(response): + return response.status_code + + return load_detail(response) + + def list_team_invitations( + self, + team_id: int, + parameters: Optional[TeamInvitationParameter] = None, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, CursorPaginatedResponse[TeamInvitation]]: + response = self.get( + endpoint=f"teams/{team_id}/team_invitations", + data=parameters.to_dict() if parameters else {}, + extra_headers=extra_headers, + ) + + obj: Union[Detail, CursorPaginatedResponse[TeamInvitation]] + if is_ok(response): + obj = CursorPaginatedResponse[TeamInvitation].from_response( + response, TeamInvitation + ) + else: + obj = load_detail(response) + + obj.status_code + return obj + + def create_team_invitation( + self, + team_id: int, + invitation: CreateTeamInvitation, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, TeamInvitation]: + response = self.post( + endpoint=f"teams/{team_id}/team_invitations", + data=CreateTeamInvitation.to_dict(invitation), + extra_headers=extra_headers, + ) + + obj: Union[Detail, TeamInvitation] + if is_create_ok(response): + obj = TeamInvitation.from_dict(response.json()) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def delete_team_invitation( + self, + team_id: int, + invitation_id: int, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, int]: + response = self.delete( + endpoint=f"teams/{team_id}/team_invitations/{invitation_id}", + extra_headers=extra_headers, + ) + + if is_delete_ok(response): + return response.status_code + + return load_detail(response) + + def list_team_members( + self, + team_id: int, + parameters: Optional[TeamMemberParameter] = None, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, CursorPaginatedResponse[TeamMember]]: + response = self.get( + endpoint=f"teams/{team_id}/team_memberships", + data=parameters.to_dict() if parameters else {}, + extra_headers=extra_headers, + ) + + obj: Union[Detail, CursorPaginatedResponse[TeamMember]] + if is_ok(response): + obj = CursorPaginatedResponse[TeamMember].from_response( + response, TeamMember + ) + else: + obj = load_detail(response) + + obj.status_code + return obj + + def create_team_member( + self, + team_id: int, + member: CreateTeamMember, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, TeamMember]: + response = self.post( + endpoint=f"teams/{team_id}/team_memberships", + data=CreateTeamMember.to_dict(member), + extra_headers=extra_headers, + ) + + obj: Union[Detail, TeamMember] + if is_create_ok(response): + obj = TeamMember.from_dict(response.json()) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def delete_team_member( + self, + team_id: int, + team_member_id: int, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, int]: + response = self.delete( + endpoint=f"teams/{team_id}/team_memberships/{team_member_id}", + extra_headers=extra_headers, + ) + + if is_delete_ok(response): + return response.status_code return load_detail(response) diff --git a/pygitguardian/config.py b/pygitguardian/config.py index 90c2d247..ddf59fa2 100644 --- a/pygitguardian/config.py +++ b/pygitguardian/config.py @@ -1,4 +1,4 @@ -DEFAULT_BASE_URI = "https://api.gitguardian.com" +DEFAULT_BASE_URI = "http://localhost:3000/exposed" DEFAULT_API_VERSION = "v1" DEFAULT_TIMEOUT = 20.0 # 20s default timeout diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 002379ee..1ca7d922 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -1237,3 +1237,241 @@ class DeleteMember(Base, FromDictMixin): marshmallow_dataclass.class_schema(DeleteMember, base_schema=BaseSchema), ) DeleteMember.SCHEMA = DeleteMemberSchema() + + +class TeamsParameter(PaginationParameter, SearchParameter, Base, FromDictMixin): + is_global: Optional[bool] = None + + +TeamsParameterSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(DeleteMember, base_schema=BaseSchema), +) +TeamsParameter.SCHEMA = TeamsParameterSchema() + + +@dataclass +class Team(Base, FromDictMixin): + id: int + name: str + is_global: bool + gitguardian_url: str + description: Optional[str] = None + + +TeamsSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(Team, base_schema=BaseSchema), +) +Team.SCHEMA = TeamsSchema() + + +@dataclass +class CreateTeam(Base, FromDictMixin): + name: str + description: Optional[str] = "" + + +class CreateTeamSchema(BaseSchema): + many = False + + name = fields.Str(required=True) + description = fields.Str(allow_none=True) + + class Meta: + exclude_none = True + + +CreateTeam.SCHEMA = CreateTeamSchema() + + +@dataclass +class UpdateTeam(Base, FromDictMixin): + id: int + name: Optional[str] + description: Optional[str] = None + + +UpdateTeamSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(UpdateTeam, base_schema=BaseSchema), +) +UpdateTeam.SCHEMA = UpdateTeamSchema() + + +class TeamPermission(str, Enum): + MANAGER = "can_manage" + MEMBER = "cannot_manage" + + +class IncidentPermission(str, Enum): + EDIT = "can_edit" + VIEW = "can_view" + FULL_ACCESS = "full_access" + + +@dataclass +class TeamInvitationParameter(PaginationParameter, Base, FromDictMixin): + invitation_id: Optional[int] = None + is_team_leader: Optional[bool] = None + incident_permission: Optional[IncidentPermission] = None + + +@dataclass +class TeamInvitationParameterSchema(BaseSchema): + invitation_id = fields.Int(allow_none=True) + is_team_leader = fields.Bool(allow_none=True) + incident_permission = fields.Enum( + IncidentPermission, by_value=True, allow_none=True + ) + + class Meta: + exclude_none = True + + +TeamInvitationParameter.SCHEMA = TeamInvitationParameterSchema() + + +@dataclass +class TeamInvitation(Base, FromDictMixin): + id: int + invitation_id: int + team_id: int + team_permission: TeamPermission + incident_permission: IncidentPermission + + +class TeamInvitationSchema(BaseSchema): + many = False + + id = fields.Int(required=True) + invitation_id = fields.Int(required=True) + team_id = fields.Int(required=True) + team_permission = fields.Enum(TeamPermission, by_value=True, required=True) + incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) + + @post_load + def return_member( + self, + data: dict[str, Any], + **kwargs: dict[str, Any], + ): + return TeamInvitation(**data) + + +TeamInvitation.SCHEMA = TeamInvitationSchema() + + +@dataclass +class CreateTeamInvitation(Base, FromDictMixin): + invitation_id: int + is_team_leader: bool + incident_permission: IncidentPermission + + +class CreateTeamInvitationSchema(BaseSchema): + many = False + + invitation_id = fields.Int(required=True) + is_team_leader = fields.Bool(required=True) + incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) + + @post_load + def return_team_invitation(self, data: dict[str, Any], **kwargs: dict[str, Any]): + return CreateTeamInvitation(**data) + + class Meta: + exclude_none = True + + +CreateTeamInvitation.SCHEMA = CreateTeamInvitationSchema() + + +class TeamMemberParameter(PaginationParameter, SearchParameter, Base, FromDictMixin): + is_team_leader: Optional[bool] = None + incident_permission: Optional[IncidentPermission] = None + member_id: Optional[int] = None + + +class TeamMembershipParameterSchema(BaseSchema): + is_team_leader = fields.Bool(allow_none=True) + incident_permission = fields.Enum( + IncidentPermission, by_value=True, allow_none=True + ) + member_id = fields.Int(allow_none=True) + + @post_load + def return_team_membership_parameter( + self, data: dict[str, Any], **kwargs: dict[str, Any] + ): + return TeamMemberParameter(**data) + + class Meta: + exclude_none = True + + +TeamMemberParameter.SCHEMA = TeamMembershipParameterSchema() + + +@dataclass +class TeamMember(Base, FromDictMixin): + id: int + team_id: int + member_id: int + is_team_leader: bool + team_permission: TeamPermission + incident_permission: IncidentPermission + + +class TeamMemberSchema(BaseSchema): + id = fields.Int(required=True) + team_id = fields.Int(required=True) + member_id = fields.Int(required=True) + is_team_leader = fields.Bool(required=True) + team_permission = fields.Enum(TeamPermission, by_value=True, required=True) + incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) + + @post_load + def return_team_membership(self, data: dict[str, Any], **kwargs: dict[str, Any]): + return TeamMember(**data) + + +TeamMember.SCHEMA = TeamMemberSchema() + + +@dataclass +class CreateTeamMemberParameter(Base, FromDictMixin): + send_email: bool + + +CreateTeamMemberParameterSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema( + CreateTeamMemberParameter, base_schema=BaseSchema + ), +) +CreateTeamMemberParameter.SCHEMA = CreateTeamMemberParameterSchema() + + +@dataclass +class CreateTeamMember(Base, FromDictMixin): + member_id: int + is_team_leader: bool + incident_permission: IncidentPermission + + +class CreateTeamMemberSchema(BaseSchema): + many = False + + member_id = fields.Int(required=True) + is_team_leader = fields.Bool(required=True) + incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) + + @post_load + def return_create_team_membership( + self, data: dict[str, Any], **kwargs: dict[str, Any] + ): + return CreateTeamMember(**data) + + +CreateTeamMember.SCHEMA = CreateTeamMemberSchema() diff --git a/tests/cassettes/test_create_team.yaml b/tests/cassettes/test_create_team.yaml new file mode 100644 index 00000000..f7986e96 --- /dev/null +++ b/tests/cassettes/test_create_team.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: '{"name": "team1", "description": ""}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '36' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: POST + uri: https://api.gitguardian.com/v1/teams + response: + body: + string: '{"id":8,"is_global":false,"name":"team1","description":"","gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/8"}' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '134' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 16:36:11 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 654ead8d7d6e98623b3b5fec06ec2a62 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 201 + message: Created +version: 1 diff --git a/tests/cassettes/test_create_team_invitation.yaml b/tests/cassettes/test_create_team_invitation.yaml new file mode 100644 index 00000000..bf1821de --- /dev/null +++ b/tests/cassettes/test_create_team_invitation.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: '{"invitation_id": 1, "is_team_leader": true, "incident_permission": "can_view"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '79' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: POST + uri: https://api.gitguardian.com/v1/teams/9/team_invitations + response: + body: + string: '{"id":1,"team_id":9,"invitation_id":1,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"can_view"}' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '124' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 17:37:44 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 9f921c4d1568b2d510214689552e8902 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 201 + message: Created +version: 1 diff --git a/tests/cassettes/test_create_team_member.yaml b/tests/cassettes/test_create_team_member.yaml new file mode 100644 index 00000000..4ceed804 --- /dev/null +++ b/tests/cassettes/test_create_team_member.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: '{"member_id": 12, "is_team_leader": false, "incident_permission": "can_view"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '77' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: POST + uri: https://api.gitguardian.com/v1/teams/9/team_memberships + response: + body: + string: '{"id":10,"team_id":9,"member_id":12,"is_team_leader":false,"team_permission":"cannot_manage","incident_permission":"can_view"}' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '126' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 18:12:24 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 166834a4962e207efa93e77c4ff4516d + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 201 + message: Created +version: 1 diff --git a/tests/cassettes/test_delete_team.yaml b/tests/cassettes/test_delete_team.yaml new file mode 100644 index 00000000..fe8e173c --- /dev/null +++ b/tests/cassettes/test_delete_team.yaml @@ -0,0 +1,54 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: DELETE + uri: https://api.gitguardian.com/v1/teams/8 + response: + body: + string: '' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '0' + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 17:00:40 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 0ba2e8d90bdab6ce346eb29288084b58 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/cassettes/test_delete_team_invitation.yaml b/tests/cassettes/test_delete_team_invitation.yaml new file mode 100644 index 00000000..f5a3b324 --- /dev/null +++ b/tests/cassettes/test_delete_team_invitation.yaml @@ -0,0 +1,54 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: DELETE + uri: https://api.gitguardian.com/v1/teams/9/team_invitations/1 + response: + body: + string: '' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - PUT, PATCH, DELETE, OPTIONS + Connection: + - keep-alive + Content-Length: + - '0' + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 17:40:00 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 4d327fbe3c97806c8c422c4faf5a5851 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/cassettes/test_delete_team_member.yaml b/tests/cassettes/test_delete_team_member.yaml new file mode 100644 index 00000000..661c0b8f --- /dev/null +++ b/tests/cassettes/test_delete_team_member.yaml @@ -0,0 +1,54 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: DELETE + uri: https://api.gitguardian.com/v1/teams/9/team_memberships/10 + response: + body: + string: '' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - PUT, PATCH, DELETE, OPTIONS + Connection: + - keep-alive + Content-Length: + - '0' + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 18:14:25 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 5def1c122ec3d1634d34a6541f4b6a72 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/cassettes/test_get_team.yaml b/tests/cassettes/test_get_team.yaml new file mode 100644 index 00000000..2f0dcd06 --- /dev/null +++ b/tests/cassettes/test_get_team.yaml @@ -0,0 +1,54 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/8 + response: + body: + string: '{"id":8,"is_global":false,"name":"team1","description":"New description","gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/8"}' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '149' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 16:52:50 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - fd2418e3d07c887e357746bb3e06f1bc + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_list_team_invitations.yaml b/tests/cassettes/test_list_team_invitations.yaml new file mode 100644 index 00000000..07640c3e --- /dev/null +++ b/tests/cassettes/test_list_team_invitations.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/9/team_invitations + response: + body: + string: '[{"id":1,"team_id":9,"invitation_id":1,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"can_view"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '126' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 17:39:11 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - bae623918f38175cfe5c3131ddbe5cc1 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_list_team_members.yaml b/tests/cassettes/test_list_team_members.yaml new file mode 100644 index 00000000..099794b6 --- /dev/null +++ b/tests/cassettes/test_list_team_members.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/9/team_memberships + response: + body: + string: '[{"id":9,"team_id":9,"member_id":6,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"full_access"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '125' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 18:11:57 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 7b6dcfa6befa810f90977de45ac5ff40 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_list_teams.yaml b/tests/cassettes/test_list_teams.yaml new file mode 100644 index 00000000..7355662d --- /dev/null +++ b/tests/cassettes/test_list_teams.yaml @@ -0,0 +1,60 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams + response: + body: + string: + '[{"id":6,"is_global":true,"name":"All incidents","description":null,"gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/6"},{"id":8,"is_global":false,"name":"team1","description":"New + description","gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/8"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '295' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 16:55:43 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 27e8974fdddaaf9e841371cdac4995ee + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_update_team.yaml b/tests/cassettes/test_update_team.yaml new file mode 100644 index 00000000..6fa60dee --- /dev/null +++ b/tests/cassettes/test_update_team.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: '{"name": "team1", "description": "New description"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '51' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: PATCH + uri: https://api.gitguardian.com/v1/teams/8 + response: + body: + string: '{"id":8,"is_global":false,"name":"team1","description":"New description","gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/8"}' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, PUT, PATCH, DELETE, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '149' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 05 Dec 2024 16:50:36 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 6997dc22059d2e12bcad60a1ca76ad5f + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_client.py b/tests/test_client.py index f59efac9..bc3d0f54 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -25,18 +25,26 @@ from pygitguardian.models import ( AccessLevel, APITokensResponse, + CreateTeam, + CreateTeamInvitation, + CreateTeamMember, CursorPaginatedResponse, DeleteMember, Detail, HoneytokenResponse, HoneytokenWithContextResponse, + IncidentPermission, JWTResponse, JWTService, Member, MultiScanResult, QuotaResponse, ScanResult, + Team, + TeamInvitation, + TeamMember, UpdateMember, + UpdateTeam, ) from pygitguardian.sca_models import ( ComputeSCAFilesResult, @@ -1464,3 +1472,166 @@ def test_delete_member(client: GGClient): result = client.delete_member(DeleteMember(id=11)) assert result == 204, result.content + + +@my_vcr.use_cassette("test_create_team.yaml", ignore_localhost=False) +def test_create_team(client: GGClient): + """ + GIVEN a client + WHEN calling POST /teams endpoint + THEN a team is created + """ + + result = client.create_team(CreateTeam(name="team1")) + + assert isinstance(result, Team), result.content + + +@my_vcr.use_cassette("test_get_team.yaml", ignore_localhost=False) +def test_get_team(client: GGClient): + """ + GIVEN a client + WHEN calling GET /teams/{id} endpoint + THEN the corresponding team is returned + """ + + result = client.get_team(8) + + assert isinstance(result, Team), result.content + + +@my_vcr.use_cassette("test_update_team.yaml", ignore_localhost=False) +def test_update_team(client: GGClient): + """ + GIVEN a client + WHEN calling PATCH /teams endpoint + THEN the corresponding team is updated + """ + + result = client.update_team( + UpdateTeam(id=8, name="team1", description="New description") + ) + + assert isinstance(result, Team), result.content + + assert result.name == "team1" + assert result.description == "New description" + + +@my_vcr.use_cassette("test_list_teams.yaml", ignore_localhost=False) +def test_list_teams(client: GGClient): + """ + GIVEN a client + WHEN calling GET /teams endpoint + THEN a paginated list of teams is returned + """ + + result = client.list_teams() + + assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result.data[0], Team) + + +@my_vcr.use_cassette("test_delete_team.yaml", ignore_localhost=False) +def test_delete_team(client: GGClient): + """ + GIVEN a client + WHEN calling DELETE /teams/{id} endpoint + THEN the team is deleted + """ + + result = client.delete_team(8) + + assert result == 204 + + +@my_vcr.use_cassette("test_create_team_invitation.yaml", ignore_localhost=False) +def test_create_team_invitation(client: GGClient): + """ + GIVEN a client + WHEN calling POST /teams/{id}/invitations endpoint + THEN an invitation is created + """ + + result = client.create_team_invitation( + 9, + CreateTeamInvitation( + invitation_id=1, + is_team_leader=True, + incident_permission=IncidentPermission.VIEW, + ), + ) + + assert isinstance(result, TeamInvitation), result.content + + +@my_vcr.use_cassette("test_list_team_invitations.yaml", ignore_localhost=False) +def test_list_team_invitations(client: GGClient): + """ + GIVEN a client + WHEN calling GET /teams/{id}/invitations endpoint + THEN a paginated list of invitations is returned + """ + + result = client.list_team_invitations(9) + + assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result.data[0], TeamInvitation) + + +@my_vcr.use_cassette("test_delete_team_invitation.yaml", ignore_localhost=False) +def test_delete_team_invitation(client: GGClient): + """ + GIVEN a client + WHEN calling DELETE /teams/{id}/invitations/{id} endpoint + THEN an invitation is deleted + """ + + result = client.delete_team_invitation(9, 1) + + assert result == 204 + + +@my_vcr.use_cassette("test_list_team_members.yaml", ignore_localhost=False) +def test_list_team_members(client: GGClient): + """ + GIVEN a client + WHEN calling GET /teams/{id}/members endpoint + THEN a paginated list of members is returned + """ + + result = client.list_team_members(9) + + assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result.data[0], TeamMember) + + +@my_vcr.use_cassette("test_create_team_member.yaml", ignore_localhost=False) +def test_create_team_member(client: GGClient): + """ + GIVEN a client + WHEN calling POST /teams/{id}/members endpoint + THEN a member is created + """ + result = client.create_team_member( + 9, + CreateTeamMember(12, False, IncidentPermission.VIEW), + ) + + assert isinstance(result, TeamMember), result.content + + assert result.incident_permission == IncidentPermission.VIEW + assert not result.is_team_leader + + +@my_vcr.use_cassette("test_delete_team_member.yaml", ignore_localhost=False) +def test_delete_team_member(client: GGClient): + """ + GIVEN a client + WHEN calling DELETE /teams/{id}/members/{id} endpoint + THEN a member is deleted + """ + + result = client.delete_team_member(9, 10) + + assert result == 204 From 8e9748dcd360a44023b6eca98ff3b99951f756d5 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Fri, 6 Dec 2024 13:56:57 +0100 Subject: [PATCH 03/20] feat(client): add sources related endpoints --- pygitguardian/client.py | 65 ++++++++++ pygitguardian/config.py | 2 +- pygitguardian/models.py | 112 ++++++++++++++-- tests/cassettes/test_add_team_sources.yaml | 122 ++++++++++++++++++ tests/cassettes/test_delete_team_sources.yaml | 121 +++++++++++++++++ tests/cassettes/test_list_sources.yaml | 64 +++++++++ tests/cassettes/test_list_teams_sources.yaml | 64 +++++++++ tests/test_client.py | 69 ++++++++++ 8 files changed, 607 insertions(+), 12 deletions(-) create mode 100644 tests/cassettes/test_add_team_sources.yaml create mode 100644 tests/cassettes/test_delete_team_sources.yaml create mode 100644 tests/cassettes/test_list_sources.yaml create mode 100644 tests/cassettes/test_list_teams_sources.yaml diff --git a/pygitguardian/client.py b/pygitguardian/client.py index 941aae94..6643dbc5 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -48,14 +48,18 @@ SecretIncident, SecretScanPreferences, ServerMetadata, + Source, + SourceParameters, Team, TeamInvitation, TeamInvitationParameter, TeamMember, TeamMemberParameter, + TeamSourceParameters, TeamsParameter, UpdateMember, UpdateTeam, + UpdateTeamSource, ) from .sca_models import ( ComputeSCAFilesResult, @@ -1213,3 +1217,64 @@ def delete_team_member( return response.status_code return load_detail(response) + + def list_sources( + self, + parameters: Optional[SourceParameters] = None, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, CursorPaginatedResponse[Source]]: + response = self.get( + endpoint="sources", + data=parameters.to_dict() if parameters else {}, + extra_headers=extra_headers, + ) + + obj: Union[Detail, CursorPaginatedResponse[Source]] + if is_ok(response): + obj = CursorPaginatedResponse[Source].from_response(response, Source) + else: + obj = load_detail(response) + + obj.status_code + return obj + + def list_teams_sources( + self, + team_id: int, + parameters: Optional[TeamSourceParameters] = None, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, CursorPaginatedResponse[Source]]: + response = self.get( + endpoint=f"teams/{team_id}/sources", + data=parameters.to_dict() if parameters else {}, + extra_headers=extra_headers, + ) + + obj: Union[Detail, CursorPaginatedResponse[Source]] + if is_ok(response): + obj = CursorPaginatedResponse[Source].from_response(response, Source) + else: + obj = load_detail(response) + + obj.status_code + return obj + + def update_team_source( + self, + team_sources: UpdateTeamSource, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, int]: + team_id = team_sources.team_id + data = team_sources.to_dict() + del data["team_id"] + + response = self.post( + endpoint=f"teams/{team_id}/sources", + data=data, + extra_headers=extra_headers, + ) + + if response.status_code == 204: + return 204 + + return load_detail(response) diff --git a/pygitguardian/config.py b/pygitguardian/config.py index ddf59fa2..90c2d247 100644 --- a/pygitguardian/config.py +++ b/pygitguardian/config.py @@ -1,4 +1,4 @@ -DEFAULT_BASE_URI = "http://localhost:3000/exposed" +DEFAULT_BASE_URI = "https://api.gitguardian.com" DEFAULT_API_VERSION = "v1" DEFAULT_TIMEOUT = 20.0 # 20s default timeout diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 1ca7d922..6ab2825c 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -982,25 +982,70 @@ class Feedback(Base, FromDictMixin): answers: List[Answer] +@dataclass +class SecretIncidentStats(Base, FromDictMixin): + total: int + severity_breakdown: dict[Severity, int] + + +@dataclass +class SecretIncidentsBreakdown(Base, FromDictMixin): + open_secret_incidents: SecretIncidentStats + closed_secret_incidents: SecretIncidentStats + + +ScanStatus = Literal[ + "pending", + "running", + "canceled", + "failed", + "too_large", + "timeout", + "pending_timeout", + "finished", +] + + +@dataclass +class Scan(Base, FromDictMixin): + date: datetime + status: ScanStatus + failing_reason: str + commits_scanned: int + branches_scanned: int + duration: str + + +SourceHealth = Literal["safe", "unknown", "at_risk"] +SourceCriticality = Literal["critical", "high", "medium", "low", "unknown"] + + @dataclass class Source(Base, FromDictMixin): id: int url: str type: str full_name: str - health: Literal["safe", "unknown", "at_risk"] + health: SourceHealth default_branch: Optional[str] default_branch_head: Optional[str] open_incidents_count: int closed_incidents_count: int - secret_incidents_breakdown: Dict[str, Any] # TODO: add SecretIncidentsBreakdown + secret_incidents_breakdown: SecretIncidentsBreakdown visibility: Visibility external_id: str - source_criticality: str - last_scan: Optional[Dict[str, Any]] # TODO: add LastScan + source_criticality: SourceCriticality + last_scan: Scan monitored: bool +SourceSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(Source, base_schema=BaseSchema), +) +Source.SCHEMA = SourceSchema() + + @dataclass class OccurrenceMatch(Base, FromDictMixin): """ @@ -1103,14 +1148,14 @@ class AccessLevel(str, Enum): RESTRICTED = "restricted" -class PaginationParameter(Base, FromDictMixin): +class PaginationParameter(ToDictMixin): """Pagination mixin used for endpoints that support pagination.""" cursor: str = "" per_page: int = 20 -class SearchParameter(Base, FromDictMixin): +class SearchParameter(ToDictMixin): search: Optional[str] = None @@ -1142,7 +1187,7 @@ def from_response( @dataclass -class MembersParameters(PaginationParameter, SearchParameter, Base, FromDictMixin): +class MembersParameters(PaginationParameter, SearchParameter, ToDictMixin): """ Members query parameters """ @@ -1239,7 +1284,7 @@ class DeleteMember(Base, FromDictMixin): DeleteMember.SCHEMA = DeleteMemberSchema() -class TeamsParameter(PaginationParameter, SearchParameter, Base, FromDictMixin): +class TeamsParameter(PaginationParameter, SearchParameter, ToDictMixin): is_global: Optional[bool] = None @@ -1311,7 +1356,7 @@ class IncidentPermission(str, Enum): @dataclass -class TeamInvitationParameter(PaginationParameter, Base, FromDictMixin): +class TeamInvitationParameter(PaginationParameter, ToDictMixin): invitation_id: Optional[int] = None is_team_leader: Optional[bool] = None incident_permission: Optional[IncidentPermission] = None @@ -1387,7 +1432,7 @@ class Meta: CreateTeamInvitation.SCHEMA = CreateTeamInvitationSchema() -class TeamMemberParameter(PaginationParameter, SearchParameter, Base, FromDictMixin): +class TeamMemberParameter(PaginationParameter, SearchParameter, ToDictMixin): is_team_leader: Optional[bool] = None incident_permission: Optional[IncidentPermission] = None member_id: Optional[int] = None @@ -1440,7 +1485,7 @@ def return_team_membership(self, data: dict[str, Any], **kwargs: dict[str, Any]) @dataclass -class CreateTeamMemberParameter(Base, FromDictMixin): +class CreateTeamMemberParameter(ToDictMixin): send_email: bool @@ -1475,3 +1520,48 @@ def return_create_team_membership( CreateTeamMember.SCHEMA = CreateTeamMemberSchema() + + +@dataclass +class TeamSourceParameters(PaginationParameter, SearchParameter, ToDictMixin): + last_scan_status: Optional[ScanStatus] = None + type: Optional[str] = None + health: Optional[SourceHealth] = None + type: Optional[str] = None + ordering: Optional[Literal["last_scan_date", "-last_scan_date"]] = None + visibility: Optional[Visibility] = None + external_id: Optional[str] = None + + +TeamSourceParametersSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(TeamSourceParameters, base_schema=BaseSchema), +) +TeamSourceParameters.SCHEMA = TeamSourceParametersSchema() + + +@dataclass +class UpdateTeamSource(Base, FromDictMixin): + team_id: int + sources_to_add: list[int] + sources_to_remove: list[int] + + +UpdateTeamSourceSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(UpdateTeamSource, base_schema=BaseSchema), +) +UpdateTeamSource.SCHEMA = UpdateTeamSourceSchema() + + +@dataclass +class SourceParameters(TeamSourceParameters): + source_criticality: Optional[SourceCriticality] = None + monitored: Optional[bool] = None + + +SourceParametersSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(SourceParameters, base_schema=BaseSchema), +) +SourceParameters.SCHEMA = SourceParametersSchema() diff --git a/tests/cassettes/test_add_team_sources.yaml b/tests/cassettes/test_add_team_sources.yaml new file mode 100644 index 00000000..703e811f --- /dev/null +++ b/tests/cassettes/test_add_team_sources.yaml @@ -0,0 +1,122 @@ +interactions: + - request: + body: '{"sources_to_add": [126], "sources_to_remove": []}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '50' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: POST + uri: https://api.gitguardian.com/v1/teams/9/sources + response: + body: + string: '' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '0' + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 12:46:14 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 05e58838d26c922add8312a362dba69c + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 204 + message: No Content + - request: + body: type=azure_devops + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '17' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/9/sources + response: + body: + string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '5158' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 12:46:14 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 235112590e3f298f700971bb283acb6c + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_delete_team_sources.yaml b/tests/cassettes/test_delete_team_sources.yaml new file mode 100644 index 00000000..cece553f --- /dev/null +++ b/tests/cassettes/test_delete_team_sources.yaml @@ -0,0 +1,121 @@ +interactions: + - request: + body: '{"sources_to_add": [], "sources_to_remove": [126]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '50' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: POST + uri: https://api.gitguardian.com/v1/teams/9/sources + response: + body: + string: '' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '0' + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 12:45:18 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - a22388028c540a672a7f0b9f9ee63406 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 204 + message: No Content + - request: + body: type=azure_devops + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '17' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/9/sources + response: + body: + string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '4304' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 12:45:18 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - b1645bd71d969137f339bf4f8adcad6e + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_list_sources.yaml b/tests/cassettes/test_list_sources.yaml new file mode 100644 index 00000000..a0b429bb --- /dev/null +++ b/tests/cassettes/test_list_sources.yaml @@ -0,0 +1,64 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/sources + response: + body: + string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '5158' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 12:32:40 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 36283900ade2c8f3e9a023e5c12a1f3e + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_list_teams_sources.yaml b/tests/cassettes/test_list_teams_sources.yaml new file mode 100644 index 00000000..8e85e58e --- /dev/null +++ b/tests/cassettes/test_list_teams_sources.yaml @@ -0,0 +1,64 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/9/sources + response: + body: + string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '5158' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 12:34:07 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - a3a02fa7fceb7f9aab20ceca3b110b6f + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_client.py b/tests/test_client.py index bc3d0f54..ca3cdbb8 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -40,11 +40,14 @@ MultiScanResult, QuotaResponse, ScanResult, + Source, Team, TeamInvitation, TeamMember, + TeamSourceParameters, UpdateMember, UpdateTeam, + UpdateTeamSource, ) from pygitguardian.sca_models import ( ComputeSCAFilesResult, @@ -1635,3 +1638,69 @@ def test_delete_team_member(client: GGClient): result = client.delete_team_member(9, 10) assert result == 204 + + +@my_vcr.use_cassette("test_list_sources.yaml", ignore_localhost=False) +def test_list_sources(client: GGClient): + """ + GIVEN a client + WHEN calling GET /sources endpoint + THEN a paginated list of sources is returned + """ + + result = client.list_sources() + assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result.data[0], Source) + + +@my_vcr.use_cassette("test_list_teams_sources.yaml", ignore_localhost=False) +def test_list_team_sources(client: GGClient): + """ + GIVEN a client + WHEN calling GET /sources endpoint + THEN a paginated list of sources is returned + """ + + result = client.list_teams_sources(9) + assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result.data[0], Source) + + +@my_vcr.use_cassette("test_delete_team_sources.yaml", ignore_localhost=False) +def test_delete_team_sources(client: GGClient): + """ + GIVEN a client + WHEN calling POST /teams/{id}/sources endpoint + THEN a source is deleted + """ + + result = client.update_team_source(UpdateTeamSource(9, [], [126])) + + assert result == 204 + + team_sources = client.list_teams_sources( + 9, TeamSourceParameters(type="azure_devops") + ) + assert isinstance(team_sources, CursorPaginatedResponse), team_sources.content + assert not any(source.id == 126 for source in team_sources.data) + + +@my_vcr.use_cassette("test_add_team_sources.yaml", ignore_localhost=False) +def test_add_team_sources(client: GGClient): + """ + GIVEN a client + WHEN calling POST /teams/{id}/sources endpoint + THEN a source is added + """ + + result = client.update_team_source( + UpdateTeamSource(9, [126], []), + ) + + assert result == 204 + + team_sources = client.list_teams_sources( + 9, TeamSourceParameters(type="azure_devops") + ) + assert isinstance(team_sources, CursorPaginatedResponse), team_sources.content + assert any(source.id == 126 for source in team_sources.data) From 85555860beac08bab83f3966c824fc7c283e4d75 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Fri, 6 Dec 2024 14:56:02 +0100 Subject: [PATCH 04/20] feat(client): add invitation related endpoints --- pygitguardian/client.py | 72 ++++++++++++++++-- pygitguardian/models.py | 75 ++++++++++++++++++- tests/cassettes/test_delete_invitation.yaml | 54 +++++++++++++ tests/cassettes/test_list_invitations.yaml | 58 ++++++++++++++ .../test_list_members_parameters.yaml | 58 ++++++++++++++ tests/cassettes/test_send_invitation.yaml | 58 ++++++++++++++ tests/test_client.py | 64 ++++++++++++++++ 7 files changed, 430 insertions(+), 9 deletions(-) create mode 100644 tests/cassettes/test_delete_invitation.yaml create mode 100644 tests/cassettes/test_list_invitations.yaml create mode 100644 tests/cassettes/test_list_members_parameters.yaml create mode 100644 tests/cassettes/test_send_invitation.yaml diff --git a/pygitguardian/client.py b/pygitguardian/client.py index 6643dbc5..6c49ce0a 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -26,6 +26,8 @@ ) from .models import ( APITokensResponse, + CreateInvitation, + CreateInvitationParameter, CreateTeam, CreateTeamInvitation, CreateTeamMember, @@ -37,6 +39,8 @@ HealthCheckResponse, HoneytokenResponse, HoneytokenWithContextResponse, + Invitation, + InvitationParameter, JWTResponse, JWTService, Member, @@ -929,7 +933,7 @@ def list_members( response = self.get( endpoint="members", - data=query_parameters.to_dict() if query_parameters else {}, + params=query_parameters.to_dict() if query_parameters else {}, extra_headers=extra_headers, ) @@ -1106,7 +1110,7 @@ def list_team_invitations( ) -> Union[Detail, CursorPaginatedResponse[TeamInvitation]]: response = self.get( endpoint=f"teams/{team_id}/team_invitations", - data=parameters.to_dict() if parameters else {}, + params=parameters.to_dict() if parameters else {}, extra_headers=extra_headers, ) @@ -1166,7 +1170,7 @@ def list_team_members( ) -> Union[Detail, CursorPaginatedResponse[TeamMember]]: response = self.get( endpoint=f"teams/{team_id}/team_memberships", - data=parameters.to_dict() if parameters else {}, + params=parameters.to_dict() if parameters else {}, extra_headers=extra_headers, ) @@ -1225,7 +1229,7 @@ def list_sources( ) -> Union[Detail, CursorPaginatedResponse[Source]]: response = self.get( endpoint="sources", - data=parameters.to_dict() if parameters else {}, + params=parameters.to_dict() if parameters else {}, extra_headers=extra_headers, ) @@ -1246,7 +1250,7 @@ def list_teams_sources( ) -> Union[Detail, CursorPaginatedResponse[Source]]: response = self.get( endpoint=f"teams/{team_id}/sources", - data=parameters.to_dict() if parameters else {}, + params=parameters.to_dict() if parameters else {}, extra_headers=extra_headers, ) @@ -1278,3 +1282,61 @@ def update_team_source( return 204 return load_detail(response) + + def list_invitations( + self, + parameters: Optional[InvitationParameter] = None, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, CursorPaginatedResponse[Invitation]]: + response = self.get( + endpoint="invitations", + params=parameters.to_dict() if parameters else {}, + extra_headers=extra_headers, + ) + + obj: Union[Detail, CursorPaginatedResponse[Invitation]] + if is_ok(response): + obj = CursorPaginatedResponse[Invitation].from_response( + response, Invitation + ) + else: + obj = load_detail(response) + + obj.status_code + return obj + + def send_invitation( + self, + invitation: CreateInvitation, + parameters: Optional[CreateInvitationParameter] = None, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, Invitation]: + response = self.post( + endpoint="invitations", + data=CreateInvitation.to_dict(invitation), + params=parameters.to_dict() if parameters else {}, + extra_headers=extra_headers, + ) + + obj: Union[Detail, Invitation] + if is_create_ok(response): + obj = Invitation.from_dict(response.json()) + else: + obj = load_detail(response) + + obj.status_code = response.status_code + return obj + + def delete_invitation( + self, + invitation_id: int, + extra_headers: Optional[Dict[str, str]] = None, + ) -> Union[Detail, int]: + response = self.delete( + endpoint=f"invitations/{invitation_id}", extra_headers=extra_headers + ) + + if is_delete_ok(response): + return response.status_code + + return load_detail(response) diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 6ab2825c..27543b1f 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -1199,10 +1199,16 @@ class MembersParameters(PaginationParameter, SearchParameter, ToDictMixin): ] = None -MembersParametersSchema = cast( - Type[BaseSchema], - marshmallow_dataclass.class_schema(MembersParameters, base_schema=BaseSchema), -) +class MembersParametersSchema(BaseSchema): + access_level = fields.Enum(AccessLevel, by_value=True, allow_none=True) + active = fields.Bool(allow_none=True) + ordering = fields.Str(allow_none=True) + + @post_load + def return_members_parameters(self, data: dict[str, Any], **kwargs: dict[str, Any]): + return MembersParameters(**data) + + MembersParameters.SCHEMA = MembersParametersSchema() @@ -1565,3 +1571,64 @@ class SourceParameters(TeamSourceParameters): marshmallow_dataclass.class_schema(SourceParameters, base_schema=BaseSchema), ) SourceParameters.SCHEMA = SourceParametersSchema() + + +@dataclass +class InvitationParameter( + PaginationParameter, SearchParameter, FromDictMixin, ToDictMixin +): + ordering: Literal["date", "-date"] + + +@dataclass +class Invitation(Base, FromDictMixin): + id: int + email: str + access_level: AccessLevel + date: datetime + + +class InvitationSchema(BaseSchema): + id = fields.Int(required=True) + email = fields.Str(required=True) + access_level = fields.Enum(AccessLevel, by_value=True, required=True) + date = fields.DateTime(required=True) + + @post_load + def return_invitation(self, data: dict[str, Any], **kwargs: dict[str, Any]): + return Invitation(**data) + + +Invitation.SCHEMA = InvitationSchema() + + +@dataclass +class CreateInvitationParameter(FromDictMixin, ToDictMixin): + send_email: Optional[bool] = None + + +CreateInvitationParameterSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema( + CreateInvitationParameter, base_schema=BaseSchema + ), +) +CreateInvitationParameter.SCHEMA = CreateInvitationParameterSchema() + + +@dataclass +class CreateInvitation(FromDictMixin, ToDictMixin): + email: str + access_level: AccessLevel + + +class CreateInvitationSchema(BaseSchema): + email = fields.Str(required=True) + access_level = fields.Enum(AccessLevel, by_value=True, required=True) + + @post_load + def return_invitation(self, data: dict[str, Any], **kwargs: dict[str, Any]): + return CreateInvitation(**data) + + +CreateInvitation.SCHEMA = CreateInvitationSchema() diff --git a/tests/cassettes/test_delete_invitation.yaml b/tests/cassettes/test_delete_invitation.yaml new file mode 100644 index 00000000..004db69a --- /dev/null +++ b/tests/cassettes/test_delete_invitation.yaml @@ -0,0 +1,54 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: DELETE + uri: https://api.gitguardian.com/v1/invitations/2 + response: + body: + string: '' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - DELETE, OPTIONS + Connection: + - keep-alive + Content-Length: + - '0' + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 13:38:57 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - fd715a797aabb2eddf49823db3e54209 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 204 + message: No Content +version: 1 diff --git a/tests/cassettes/test_list_invitations.yaml b/tests/cassettes/test_list_invitations.yaml new file mode 100644 index 00000000..140008d4 --- /dev/null +++ b/tests/cassettes/test_list_invitations.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/invitations + response: + body: + string: '[{"id":1,"date":"2024-12-05T17:35:12.273332Z","email":"toto+test@gg.com","role":"member","access_level":"member"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '114' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 13:33:31 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 182ad8123736b9315def7a957569ef89 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_list_members_parameters.yaml b/tests/cassettes/test_list_members_parameters.yaml new file mode 100644 index 00000000..ec06dfe1 --- /dev/null +++ b/tests/cassettes/test_list_members_parameters.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/members?access_level=manager + response: + body: + string: '[]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 13:53:52 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 345625b0c1e6c814a9b997bd7ea3ea96 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_send_invitation.yaml b/tests/cassettes/test_send_invitation.yaml new file mode 100644 index 00000000..f1927556 --- /dev/null +++ b/tests/cassettes/test_send_invitation.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: '{"email": "owl@example.com", "access_level": "member"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '54' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: POST + uri: https://api.gitguardian.com/v1/invitations?send_email=False + response: + body: + string: '{"id":2,"date":"2024-12-06T13:35:36.737833Z","email":"owl@example.com","role":"member","access_level":"member"}' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '111' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 13:35:36 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 2e6e4d6e2cb571015e6c9949fa6c431f + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 201 + message: Created +version: 1 diff --git a/tests/test_client.py b/tests/test_client.py index ca3cdbb8..723db3e0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -25,6 +25,8 @@ from pygitguardian.models import ( AccessLevel, APITokensResponse, + CreateInvitation, + CreateInvitationParameter, CreateTeam, CreateTeamInvitation, CreateTeamMember, @@ -34,9 +36,11 @@ HoneytokenResponse, HoneytokenWithContextResponse, IncidentPermission, + Invitation, JWTResponse, JWTService, Member, + MembersParameters, MultiScanResult, QuotaResponse, ScanResult, @@ -1446,6 +1450,21 @@ def test_list_members(client: GGClient): assert isinstance(result, CursorPaginatedResponse), result.content +@my_vcr.use_cassette("test_list_members_parameters.yaml", ignore_localhost=False) +def test_search_member(client: GGClient): + """ + GIVEN a client + WHEN calling /members endpoint + AND parameters are passed + THEN it returns a paginated list of members matching the parameters + """ + + result = client.list_members(MembersParameters(access_level=AccessLevel.MANAGER)) + + assert isinstance(result, CursorPaginatedResponse), result.content + assert all(member.access_level == AccessLevel.MANAGER for member in result.data) + + @my_vcr.use_cassette("test_update_member.yaml", ignore_localhost=False) def test_update_member(client: GGClient): """ @@ -1704,3 +1723,48 @@ def test_add_team_sources(client: GGClient): ) assert isinstance(team_sources, CursorPaginatedResponse), team_sources.content assert any(source.id == 126 for source in team_sources.data) + + +@my_vcr.use_cassette("test_list_invitations.yaml", ignore_localhost=False) +def test_list_invitations(client: GGClient): + """ + GIVEN a client + WHEN calling GET /invitations endpoint + THEN a paginated list of invitations is returned + """ + + result = client.list_invitations() + assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result.data[0], Invitation) + + +@my_vcr.use_cassette("test_send_invitation.yaml", ignore_localhost=False) +def test_send_invitation(client: GGClient): + """ + GIVEN a client + WHEN calling POST /invitations endpoint + THEN an invitation is sent + """ + + result = client.send_invitation( + CreateInvitation(email="owl@example.com", access_level=AccessLevel.MEMBER), + CreateInvitationParameter(send_email=False), + ) + + assert isinstance(result, Invitation), result.content + + assert result.email == "owl@example.com" + assert result.access_level == AccessLevel.MEMBER + + +@my_vcr.use_cassette("test_delete_invitation.yaml", ignore_localhost=False) +def test_delete_invitation(client: GGClient): + """ + GIVEN a client + WHEN calling DELETE /invitations/{id} endpoint + THEN an invitation is deleted + """ + + result = client.delete_invitation(2) + + assert result == 204 From a932163ba9e4368d51c6d25b238b5424e283fc4f Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Fri, 6 Dec 2024 15:00:18 +0100 Subject: [PATCH 05/20] docs(changelog): added changelog for teams, members, invitations and sources endpoints --- ..._yanne.bensafia_members_teams_endpoints.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 changelog.d/20241206_145636_yanne.bensafia_members_teams_endpoints.md diff --git a/changelog.d/20241206_145636_yanne.bensafia_members_teams_endpoints.md b/changelog.d/20241206_145636_yanne.bensafia_members_teams_endpoints.md new file mode 100644 index 00000000..25253503 --- /dev/null +++ b/changelog.d/20241206_145636_yanne.bensafia_members_teams_endpoints.md @@ -0,0 +1,43 @@ + + + + +### Added + +- Added support for members and teams endpoints. +- Added support for invitations endpoints. +- Added support for sources endpoints. + + + + + From b5f7f6b4d00812f9438304c6cd305f6147ca6ff5 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Fri, 6 Dec 2024 15:44:20 +0100 Subject: [PATCH 06/20] fix(models): changed wrong keyword argument for parameters schema and usages --- pygitguardian/client.py | 6 +- pygitguardian/models.py | 44 ++++---- tests/cassettes/test_add_team_sources.yaml | 16 +-- .../test_create_team_member_parameters.yaml | 58 ++++++++++ tests/cassettes/test_delete_team_sources.yaml | 16 +-- tests/cassettes/test_global_team.yaml | 58 ++++++++++ tests/cassettes/test_search_sources.yaml | 64 +++++++++++ .../test_search_team_invitations.yaml | 58 ++++++++++ tests/cassettes/test_search_team_members.yaml | 58 ++++++++++ .../cassettes/test_search_teams_sources.yaml | 64 +++++++++++ tests/test_client.py | 105 ++++++++++++++++++ 11 files changed, 504 insertions(+), 43 deletions(-) create mode 100644 tests/cassettes/test_create_team_member_parameters.yaml create mode 100644 tests/cassettes/test_global_team.yaml create mode 100644 tests/cassettes/test_search_sources.yaml create mode 100644 tests/cassettes/test_search_team_invitations.yaml create mode 100644 tests/cassettes/test_search_team_members.yaml create mode 100644 tests/cassettes/test_search_teams_sources.yaml diff --git a/pygitguardian/client.py b/pygitguardian/client.py index 6c49ce0a..1d6bb573 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -31,6 +31,7 @@ CreateTeam, CreateTeamInvitation, CreateTeamMember, + CreateTeamMemberParameter, CursorPaginatedResponse, DeleteMember, Detail, @@ -1011,9 +1012,10 @@ def list_teams( parameters: Optional[TeamsParameter] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, CursorPaginatedResponse[Team]]: + params = parameters.to_dict() if parameters else {} response = self.get( endpoint="teams", - data=parameters.to_dict() if parameters else {}, + params=params, extra_headers=extra_headers, ) @@ -1189,11 +1191,13 @@ def create_team_member( self, team_id: int, member: CreateTeamMember, + parameters: Optional[CreateTeamMemberParameter] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, TeamMember]: response = self.post( endpoint=f"teams/{team_id}/team_memberships", data=CreateTeamMember.to_dict(member), + params=parameters.to_dict() if parameters else {}, extra_headers=extra_headers, ) diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 27543b1f..1fdea965 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -985,7 +985,7 @@ class Feedback(Base, FromDictMixin): @dataclass class SecretIncidentStats(Base, FromDictMixin): total: int - severity_breakdown: dict[Severity, int] + severity_breakdown: Dict[Severity, int] @dataclass @@ -1165,7 +1165,7 @@ class SearchParameter(ToDictMixin): @dataclass class CursorPaginatedResponse(Generic[PaginatedData]): status_code: int - data: list[PaginatedData] + data: List[PaginatedData] prev: Optional[str] = None next: Optional[str] = None @@ -1174,7 +1174,7 @@ def from_response( cls, response: "requests.Response", data_type: Type[PaginatedData] ) -> "CursorPaginatedResponse[PaginatedData]": data = cast( - list[PaginatedData], data_type.from_dict(response.json(), many=True) + List[PaginatedData], data_type.from_dict(response.json(), many=True) ) paginated_response = cls(status_code=response.status_code, data=data) @@ -1205,7 +1205,7 @@ class MembersParametersSchema(BaseSchema): ordering = fields.Str(allow_none=True) @post_load - def return_members_parameters(self, data: dict[str, Any], **kwargs: dict[str, Any]): + def return_members_parameters(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): return MembersParameters(**data) @@ -1239,10 +1239,9 @@ class MemberSchema(BaseSchema): @post_load def return_member( self, - data: list[dict[str, Any]] | dict[str, Any], - **kwargs: dict[str, Any], + data: Dict[str, Any], + **kwargs: Dict[str, Any], ): - data = cast(dict[str, Any], data) return Member(**data) @@ -1256,8 +1255,8 @@ class UpdateMemberSchema(BaseSchema): @post_dump def access_level_value( - self, data: dict[str, Any], **kwargs: dict[str, Any] - ) -> dict[str, Any]: + self, data: Dict[str, Any], **kwargs: Dict[str, Any] + ) -> Dict[str, Any]: if "access_level" in data: data["access_level"] = AccessLevel(data["access_level"]).value return data @@ -1290,13 +1289,14 @@ class DeleteMember(Base, FromDictMixin): DeleteMember.SCHEMA = DeleteMemberSchema() -class TeamsParameter(PaginationParameter, SearchParameter, ToDictMixin): +@dataclass +class TeamsParameter(PaginationParameter, SearchParameter, FromDictMixin, ToDictMixin): is_global: Optional[bool] = None TeamsParameterSchema = cast( Type[BaseSchema], - marshmallow_dataclass.class_schema(DeleteMember, base_schema=BaseSchema), + marshmallow_dataclass.class_schema(TeamsParameter, base_schema=BaseSchema), ) TeamsParameter.SCHEMA = TeamsParameterSchema() @@ -1368,7 +1368,6 @@ class TeamInvitationParameter(PaginationParameter, ToDictMixin): incident_permission: Optional[IncidentPermission] = None -@dataclass class TeamInvitationParameterSchema(BaseSchema): invitation_id = fields.Int(allow_none=True) is_team_leader = fields.Bool(allow_none=True) @@ -1404,8 +1403,8 @@ class TeamInvitationSchema(BaseSchema): @post_load def return_member( self, - data: dict[str, Any], - **kwargs: dict[str, Any], + data: Dict[str, Any], + **kwargs: Dict[str, Any], ): return TeamInvitation(**data) @@ -1428,7 +1427,7 @@ class CreateTeamInvitationSchema(BaseSchema): incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) @post_load - def return_team_invitation(self, data: dict[str, Any], **kwargs: dict[str, Any]): + def return_team_invitation(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): return CreateTeamInvitation(**data) class Meta: @@ -1438,6 +1437,7 @@ class Meta: CreateTeamInvitation.SCHEMA = CreateTeamInvitationSchema() +@dataclass class TeamMemberParameter(PaginationParameter, SearchParameter, ToDictMixin): is_team_leader: Optional[bool] = None incident_permission: Optional[IncidentPermission] = None @@ -1453,7 +1453,7 @@ class TeamMembershipParameterSchema(BaseSchema): @post_load def return_team_membership_parameter( - self, data: dict[str, Any], **kwargs: dict[str, Any] + self, data: Dict[str, Any], **kwargs: Dict[str, Any] ): return TeamMemberParameter(**data) @@ -1483,7 +1483,7 @@ class TeamMemberSchema(BaseSchema): incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) @post_load - def return_team_membership(self, data: dict[str, Any], **kwargs: dict[str, Any]): + def return_team_membership(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): return TeamMember(**data) @@ -1520,7 +1520,7 @@ class CreateTeamMemberSchema(BaseSchema): @post_load def return_create_team_membership( - self, data: dict[str, Any], **kwargs: dict[str, Any] + self, data: Dict[str, Any], **kwargs: Dict[str, Any] ): return CreateTeamMember(**data) @@ -1549,8 +1549,8 @@ class TeamSourceParameters(PaginationParameter, SearchParameter, ToDictMixin): @dataclass class UpdateTeamSource(Base, FromDictMixin): team_id: int - sources_to_add: list[int] - sources_to_remove: list[int] + sources_to_add: List[int] + sources_to_remove: List[int] UpdateTeamSourceSchema = cast( @@ -1595,7 +1595,7 @@ class InvitationSchema(BaseSchema): date = fields.DateTime(required=True) @post_load - def return_invitation(self, data: dict[str, Any], **kwargs: dict[str, Any]): + def return_invitation(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): return Invitation(**data) @@ -1627,7 +1627,7 @@ class CreateInvitationSchema(BaseSchema): access_level = fields.Enum(AccessLevel, by_value=True, required=True) @post_load - def return_invitation(self, data: dict[str, Any], **kwargs: dict[str, Any]): + def return_invitation(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): return CreateInvitation(**data) diff --git a/tests/cassettes/test_add_team_sources.yaml b/tests/cassettes/test_add_team_sources.yaml index 703e811f..5278f8b1 100644 --- a/tests/cassettes/test_add_team_sources.yaml +++ b/tests/cassettes/test_add_team_sources.yaml @@ -31,7 +31,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 12:46:14 GMT + - Fri, 06 Dec 2024 15:56:34 GMT Referrer-Policy: - same-origin Server: @@ -45,7 +45,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 05e58838d26c922add8312a362dba69c + - 22643c94715fcf85df077d4b68ab6541 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: @@ -54,7 +54,7 @@ interactions: code: 204 message: No Content - request: - body: type=azure_devops + body: null headers: Accept: - '*/*' @@ -62,14 +62,10 @@ interactions: - gzip, deflate Connection: - keep-alive - Content-Length: - - '17' - Content-Type: - - application/x-www-form-urlencoded User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/sources + uri: https://api.gitguardian.com/v1/teams/9/sources?type=azure_devops response: body: string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / @@ -93,7 +89,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 12:46:14 GMT + - Fri, 06 Dec 2024 15:56:34 GMT Link: - '' Referrer-Policy: @@ -111,7 +107,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 235112590e3f298f700971bb283acb6c + - 69887de8037bf0eba9dadb62171e5889 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_create_team_member_parameters.yaml b/tests/cassettes/test_create_team_member_parameters.yaml new file mode 100644 index 00000000..563972a2 --- /dev/null +++ b/tests/cassettes/test_create_team_member_parameters.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: '{"member_id": 12, "is_team_leader": false, "incident_permission": "can_view"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '77' + Content-Type: + - application/json + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: POST + uri: https://api.gitguardian.com/v1/teams/9/team_memberships?send_email=False + response: + body: + string: '{"id":11,"team_id":9,"member_id":12,"is_team_leader":false,"team_permission":"cannot_manage","incident_permission":"can_view"}' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '126' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 14:37:57 GMT + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-ID: + - 9bdfa17e97c34bd4496abfdc27eb8553 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 201 + message: Created +version: 1 diff --git a/tests/cassettes/test_delete_team_sources.yaml b/tests/cassettes/test_delete_team_sources.yaml index cece553f..56cf1f60 100644 --- a/tests/cassettes/test_delete_team_sources.yaml +++ b/tests/cassettes/test_delete_team_sources.yaml @@ -31,7 +31,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 12:45:18 GMT + - Fri, 06 Dec 2024 15:53:15 GMT Referrer-Policy: - same-origin Server: @@ -45,7 +45,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - a22388028c540a672a7f0b9f9ee63406 + - c40c97ffa8505619495c4656de9772e8 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: @@ -54,7 +54,7 @@ interactions: code: 204 message: No Content - request: - body: type=azure_devops + body: null headers: Accept: - '*/*' @@ -62,14 +62,10 @@ interactions: - gzip, deflate Connection: - keep-alive - Content-Length: - - '17' - Content-Type: - - application/x-www-form-urlencoded User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/sources + uri: https://api.gitguardian.com/v1/teams/9/sources?type=azure_devops response: body: string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / @@ -92,7 +88,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 12:45:18 GMT + - Fri, 06 Dec 2024 15:53:15 GMT Link: - '' Referrer-Policy: @@ -110,7 +106,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - b1645bd71d969137f339bf4f8adcad6e + - fdcab3ffe17d5cc19ce6471a37f3ac25 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_global_team.yaml b/tests/cassettes/test_global_team.yaml new file mode 100644 index 00000000..dad046bd --- /dev/null +++ b/tests/cassettes/test_global_team.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=True + response: + body: + string: '[{"id":6,"is_global":true,"name":"All incidents","description":null,"gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/6"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '145' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 14:26:03 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 4f67e29113b6a59a311497ba05953fda + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_search_sources.yaml b/tests/cassettes/test_search_sources.yaml new file mode 100644 index 00000000..6dd9119e --- /dev/null +++ b/tests/cassettes/test_search_sources.yaml @@ -0,0 +1,64 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/sources?type=azure_devops + response: + body: + string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '5158' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 14:41:54 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 61df3a3b0044668d310ce7357488d6d8 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_search_team_invitations.yaml b/tests/cassettes/test_search_team_invitations.yaml new file mode 100644 index 00000000..77f1cbec --- /dev/null +++ b/tests/cassettes/test_search_team_invitations.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/9/team_invitations?incident_permission=can_view + response: + body: + string: '[]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 14:33:04 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - c9f8aabf9f28131533330653571aef49 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_search_team_members.yaml b/tests/cassettes/test_search_team_members.yaml new file mode 100644 index 00000000..be36304b --- /dev/null +++ b/tests/cassettes/test_search_team_members.yaml @@ -0,0 +1,58 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/9/team_memberships?is_team_leader=True + response: + body: + string: '[{"id":9,"team_id":9,"member_id":6,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"full_access"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '125' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 14:34:43 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 6218af1c35246a5a755f460d609308fd + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_search_teams_sources.yaml b/tests/cassettes/test_search_teams_sources.yaml new file mode 100644 index 00000000..b8eb5127 --- /dev/null +++ b/tests/cassettes/test_search_teams_sources.yaml @@ -0,0 +1,64 @@ +interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/9/sources?type=azure_devops + response: + body: + string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '5158' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 06 Dec 2024 14:40:26 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - d8e9c5602fb22289ffd73e5f8d1b3eec + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_client.py b/tests/test_client.py index 723db3e0..48f2ae02 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -30,6 +30,7 @@ CreateTeam, CreateTeamInvitation, CreateTeamMember, + CreateTeamMemberParameter, CursorPaginatedResponse, DeleteMember, Detail, @@ -45,10 +46,14 @@ QuotaResponse, ScanResult, Source, + SourceParameters, Team, TeamInvitation, + TeamInvitationParameter, TeamMember, + TeamMemberParameter, TeamSourceParameters, + TeamsParameter, UpdateMember, UpdateTeam, UpdateTeamSource, @@ -1554,6 +1559,22 @@ def test_list_teams(client: GGClient): assert isinstance(result.data[0], Team) +@my_vcr.use_cassette("test_global_team.yaml", ignore_localhost=False) +def test_global_team(client: GGClient): + """ + GIVEN a client + WHEN calling GET /teams endpoint + AND passing is_global parameter + THEN the global team is returned + """ + + result = client.list_teams(parameters=TeamsParameter(is_global=True)) + + assert isinstance(result, CursorPaginatedResponse), result.content + + assert all(team.is_global for team in result.data) + + @my_vcr.use_cassette("test_delete_team.yaml", ignore_localhost=False) def test_delete_team(client: GGClient): """ @@ -1601,6 +1622,26 @@ def test_list_team_invitations(client: GGClient): assert isinstance(result.data[0], TeamInvitation) +@my_vcr.use_cassette("test_search_team_invitations.yaml", ignore_localhost=False) +def test_search_team_invitations(client: GGClient): + """ + GIVEN a client + WHEN calling GET /teams/{id}/invitations endpoint + AND parameters are passed + THEN a paginated list of invitations is returned matching the parameters + """ + + result = client.list_team_invitations( + 9, + parameters=TeamInvitationParameter(incident_permission=IncidentPermission.VIEW), + ) + + assert isinstance(result, CursorPaginatedResponse), result.content + assert all( + invitation.incident_permission == "can_view" for invitation in result.data + ) + + @my_vcr.use_cassette("test_delete_team_invitation.yaml", ignore_localhost=False) def test_delete_team_invitation(client: GGClient): """ @@ -1628,6 +1669,23 @@ def test_list_team_members(client: GGClient): assert isinstance(result.data[0], TeamMember) +@my_vcr.use_cassette("test_search_team_members.yaml", ignore_localhost=False) +def test_search_team_members(client: GGClient): + """ + GIVEN a client + WHEN calling GET /teams/{id}/members endpoint + AND parameters are passed + THEN a paginated list of members is returned matching the parameters + """ + + result = client.list_team_members( + 9, parameters=TeamMemberParameter(is_team_leader=True) + ) + + assert isinstance(result, CursorPaginatedResponse), result.content + assert all(member.is_team_leader for member in result.data) + + @my_vcr.use_cassette("test_create_team_member.yaml", ignore_localhost=False) def test_create_team_member(client: GGClient): """ @@ -1646,6 +1704,23 @@ def test_create_team_member(client: GGClient): assert not result.is_team_leader +@my_vcr.use_cassette("test_create_team_member_parameters.yaml", ignore_localhost=False) +def test_create_team_member_without_mail(client: GGClient): + """ + GIVEN a client + WHEN calling POST /teams/{id}/members endpoint + THEN a member is created + """ + + result = client.create_team_member( + 9, + CreateTeamMember(12, False, IncidentPermission.VIEW), + CreateTeamMemberParameter(send_email=False), + ) + + assert isinstance(result, TeamMember), result.content + + @my_vcr.use_cassette("test_delete_team_member.yaml", ignore_localhost=False) def test_delete_team_member(client: GGClient): """ @@ -1672,6 +1747,21 @@ def test_list_sources(client: GGClient): assert isinstance(result.data[0], Source) +@my_vcr.use_cassette("test_search_sources.yaml", ignore_localhost=False) +def test_search_sources(client: GGClient): + """ + GIVEN a client + WHEN calling GET /sources endpoint + AND parameters are passed + THEN a paginated list of sources is returned matching the parameters + """ + + result = client.list_sources(parameters=SourceParameters(type="azure_devops")) + + assert isinstance(result, CursorPaginatedResponse), result.content + assert all(source.type == "azure_devops" for source in result.data) + + @my_vcr.use_cassette("test_list_teams_sources.yaml", ignore_localhost=False) def test_list_team_sources(client: GGClient): """ @@ -1685,6 +1775,21 @@ def test_list_team_sources(client: GGClient): assert isinstance(result.data[0], Source) +@my_vcr.use_cassette("test_search_teams_sources.yaml", ignore_localhost=False) +def test_search_team_sources(client: GGClient): + """ + GIVEN a client + WHEN calling GET /sources endpoint + AND parameters are passed + THEN a paginated list of sources is returned matching the parameters + """ + + result = client.list_teams_sources(9, TeamSourceParameters(type="azure_devops")) + + assert isinstance(result, CursorPaginatedResponse), result.content + assert all(source.type == "azure_devops" for source in result.data) + + @my_vcr.use_cassette("test_delete_team_sources.yaml", ignore_localhost=False) def test_delete_team_sources(client: GGClient): """ From 009bcadd515be031d3dce675afd64d68a32857d2 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 11 Dec 2024 15:52:17 +0100 Subject: [PATCH 07/20] refactor(client): delete methods now return None in case deletion was successful --- pygitguardian/client.py | 41 +++++++++++++++-------------------------- tests/test_client.py | 10 +++++----- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/pygitguardian/client.py b/pygitguardian/client.py index 1d6bb573..e192b2ff 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -142,7 +142,6 @@ def is_create_ok(resp: Response) -> bool: def is_delete_ok(resp: Response) -> bool: """ is_delete_ok returns True if the API returns code 204 - and the content type is JSON. """ return resp.status_code == codes.no_content @@ -992,7 +991,7 @@ def delete_member( self, member: DeleteMember, extra_headers: Optional[Dict[str, str]] = None, - ) -> Union[Detail, int]: + ) -> Optional[Detail]: member_id = member.id data = member.to_dict() del data["id"] @@ -1002,10 +1001,8 @@ def delete_member( ) # We bypass `is_ok` because the response content type is none - if is_delete_ok(response): - return response.status_code - - return load_detail(response) + if not is_delete_ok(response): + return load_detail(response) def list_teams( self, @@ -1093,16 +1090,14 @@ def delete_team( self, team_id: int, extra_headers: Optional[Dict[str, str]] = None, - ) -> Union[Detail, int]: + ) -> Optional[Detail]: response = self.delete( endpoint=f"teams/{team_id}", extra_headers=extra_headers, ) - if is_delete_ok(response): - return response.status_code - - return load_detail(response) + if not is_delete_ok(response): + return load_detail(response) def list_team_invitations( self, @@ -1153,16 +1148,14 @@ def delete_team_invitation( team_id: int, invitation_id: int, extra_headers: Optional[Dict[str, str]] = None, - ) -> Union[Detail, int]: + ) -> Optional[Detail]: response = self.delete( endpoint=f"teams/{team_id}/team_invitations/{invitation_id}", extra_headers=extra_headers, ) - if is_delete_ok(response): - return response.status_code - - return load_detail(response) + if not is_delete_ok(response): + return load_detail(response) def list_team_members( self, @@ -1215,16 +1208,14 @@ def delete_team_member( team_id: int, team_member_id: int, extra_headers: Optional[Dict[str, str]] = None, - ) -> Union[Detail, int]: + ) -> Optional[Detail]: response = self.delete( endpoint=f"teams/{team_id}/team_memberships/{team_member_id}", extra_headers=extra_headers, ) - if is_delete_ok(response): - return response.status_code - - return load_detail(response) + if not is_delete_ok(response): + return load_detail(response) def list_sources( self, @@ -1335,12 +1326,10 @@ def delete_invitation( self, invitation_id: int, extra_headers: Optional[Dict[str, str]] = None, - ) -> Union[Detail, int]: + ) -> Optional[Detail]: response = self.delete( endpoint=f"invitations/{invitation_id}", extra_headers=extra_headers ) - if is_delete_ok(response): - return response.status_code - - return load_detail(response) + if not is_delete_ok(response): + return load_detail(response) diff --git a/tests/test_client.py b/tests/test_client.py index 48f2ae02..7ef9aed4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1498,7 +1498,7 @@ def test_delete_member(client: GGClient): result = client.delete_member(DeleteMember(id=11)) - assert result == 204, result.content + assert result is None, result.content @my_vcr.use_cassette("test_create_team.yaml", ignore_localhost=False) @@ -1585,7 +1585,7 @@ def test_delete_team(client: GGClient): result = client.delete_team(8) - assert result == 204 + assert result is None @my_vcr.use_cassette("test_create_team_invitation.yaml", ignore_localhost=False) @@ -1652,7 +1652,7 @@ def test_delete_team_invitation(client: GGClient): result = client.delete_team_invitation(9, 1) - assert result == 204 + assert result is None @my_vcr.use_cassette("test_list_team_members.yaml", ignore_localhost=False) @@ -1731,7 +1731,7 @@ def test_delete_team_member(client: GGClient): result = client.delete_team_member(9, 10) - assert result == 204 + assert result is None @my_vcr.use_cassette("test_list_sources.yaml", ignore_localhost=False) @@ -1872,4 +1872,4 @@ def test_delete_invitation(client: GGClient): result = client.delete_invitation(2) - assert result == 204 + assert result is None From f59f4a15139a7e69fa756ef0e0fc660832b69b39 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 11 Dec 2024 15:58:34 +0100 Subject: [PATCH 08/20] refactor(models): remove many arguments in from_dict and move list serialization to from_response --- pygitguardian/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 1fdea965..ee63d0e8 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -71,8 +71,8 @@ class FromDictMixin: SCHEMA: ClassVar[Schema] @classmethod - def from_dict(cls, dct: Dict[str, Any], many: Optional[bool] = None) -> Self: - return cast(Self, cls.SCHEMA.load(dct, many=many)) + def from_dict(cls, dct: Dict[str, Any]) -> Self: + return cast(Self, cls.SCHEMA.load(dct)) class BaseSchema(Schema): @@ -1174,7 +1174,7 @@ def from_response( cls, response: "requests.Response", data_type: Type[PaginatedData] ) -> "CursorPaginatedResponse[PaginatedData]": data = cast( - List[PaginatedData], data_type.from_dict(response.json(), many=True) + List[PaginatedData], [data_type.from_dict(obj) for obj in response.json()] ) paginated_response = cls(status_code=response.status_code, data=data) From a672070675d2e9bcb9815e19dd7245859af74f1e Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 11 Dec 2024 16:02:18 +0100 Subject: [PATCH 09/20] fix(models): mark last_scan as optional for a scan object --- pygitguardian/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygitguardian/models.py b/pygitguardian/models.py index ee63d0e8..a2017367 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -1035,7 +1035,7 @@ class Source(Base, FromDictMixin): visibility: Visibility external_id: str source_criticality: SourceCriticality - last_scan: Scan + last_scan: Optional[Scan] monitored: bool From 27bc0678f8dcdd2769ac05b5030e5c1791852584 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 11 Dec 2024 16:15:47 +0100 Subject: [PATCH 10/20] test(client): changed error message to detail --- tests/test_client.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 7ef9aed4..08d95f29 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1452,7 +1452,7 @@ def test_list_members(client: GGClient): result = client.list_members() - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result @my_vcr.use_cassette("test_list_members_parameters.yaml", ignore_localhost=False) @@ -1466,7 +1466,7 @@ def test_search_member(client: GGClient): result = client.list_members(MembersParameters(access_level=AccessLevel.MANAGER)) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert all(member.access_level == AccessLevel.MANAGER for member in result.data) @@ -1482,7 +1482,7 @@ def test_update_member(client: GGClient): UpdateMember(id=10, access_level=AccessLevel.MEMBER, active=False) ) - assert isinstance(result, Member), result.content + assert isinstance(result, Member), result assert not result.active assert result.access_level == AccessLevel.MEMBER @@ -1498,7 +1498,7 @@ def test_delete_member(client: GGClient): result = client.delete_member(DeleteMember(id=11)) - assert result is None, result.content + assert result is None, result @my_vcr.use_cassette("test_create_team.yaml", ignore_localhost=False) @@ -1511,7 +1511,7 @@ def test_create_team(client: GGClient): result = client.create_team(CreateTeam(name="team1")) - assert isinstance(result, Team), result.content + assert isinstance(result, Team), result @my_vcr.use_cassette("test_get_team.yaml", ignore_localhost=False) @@ -1524,7 +1524,7 @@ def test_get_team(client: GGClient): result = client.get_team(8) - assert isinstance(result, Team), result.content + assert isinstance(result, Team), result @my_vcr.use_cassette("test_update_team.yaml", ignore_localhost=False) @@ -1539,7 +1539,7 @@ def test_update_team(client: GGClient): UpdateTeam(id=8, name="team1", description="New description") ) - assert isinstance(result, Team), result.content + assert isinstance(result, Team), result assert result.name == "team1" assert result.description == "New description" @@ -1555,7 +1555,7 @@ def test_list_teams(client: GGClient): result = client.list_teams() - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert isinstance(result.data[0], Team) @@ -1570,7 +1570,7 @@ def test_global_team(client: GGClient): result = client.list_teams(parameters=TeamsParameter(is_global=True)) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert all(team.is_global for team in result.data) @@ -1605,7 +1605,7 @@ def test_create_team_invitation(client: GGClient): ), ) - assert isinstance(result, TeamInvitation), result.content + assert isinstance(result, TeamInvitation), result @my_vcr.use_cassette("test_list_team_invitations.yaml", ignore_localhost=False) @@ -1618,7 +1618,7 @@ def test_list_team_invitations(client: GGClient): result = client.list_team_invitations(9) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert isinstance(result.data[0], TeamInvitation) @@ -1636,7 +1636,7 @@ def test_search_team_invitations(client: GGClient): parameters=TeamInvitationParameter(incident_permission=IncidentPermission.VIEW), ) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert all( invitation.incident_permission == "can_view" for invitation in result.data ) @@ -1665,7 +1665,7 @@ def test_list_team_members(client: GGClient): result = client.list_team_members(9) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert isinstance(result.data[0], TeamMember) @@ -1682,7 +1682,7 @@ def test_search_team_members(client: GGClient): 9, parameters=TeamMemberParameter(is_team_leader=True) ) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert all(member.is_team_leader for member in result.data) @@ -1698,7 +1698,7 @@ def test_create_team_member(client: GGClient): CreateTeamMember(12, False, IncidentPermission.VIEW), ) - assert isinstance(result, TeamMember), result.content + assert isinstance(result, TeamMember), result assert result.incident_permission == IncidentPermission.VIEW assert not result.is_team_leader @@ -1718,7 +1718,7 @@ def test_create_team_member_without_mail(client: GGClient): CreateTeamMemberParameter(send_email=False), ) - assert isinstance(result, TeamMember), result.content + assert isinstance(result, TeamMember), result @my_vcr.use_cassette("test_delete_team_member.yaml", ignore_localhost=False) @@ -1743,7 +1743,7 @@ def test_list_sources(client: GGClient): """ result = client.list_sources() - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert isinstance(result.data[0], Source) @@ -1758,7 +1758,7 @@ def test_search_sources(client: GGClient): result = client.list_sources(parameters=SourceParameters(type="azure_devops")) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert all(source.type == "azure_devops" for source in result.data) @@ -1771,7 +1771,7 @@ def test_list_team_sources(client: GGClient): """ result = client.list_teams_sources(9) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert isinstance(result.data[0], Source) @@ -1786,7 +1786,7 @@ def test_search_team_sources(client: GGClient): result = client.list_teams_sources(9, TeamSourceParameters(type="azure_devops")) - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert all(source.type == "azure_devops" for source in result.data) @@ -1839,7 +1839,7 @@ def test_list_invitations(client: GGClient): """ result = client.list_invitations() - assert isinstance(result, CursorPaginatedResponse), result.content + assert isinstance(result, CursorPaginatedResponse), result assert isinstance(result.data[0], Invitation) @@ -1856,7 +1856,7 @@ def test_send_invitation(client: GGClient): CreateInvitationParameter(send_email=False), ) - assert isinstance(result, Invitation), result.content + assert isinstance(result, Invitation), result assert result.email == "owl@example.com" assert result.access_level == AccessLevel.MEMBER From 3e17f92259c14a005b4b6b90b210687c035f84af Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 11 Dec 2024 16:45:30 +0100 Subject: [PATCH 11/20] refactor(models): move schema, parameters and paginated response to models_utils --- pygitguardian/client.py | 2 +- pygitguardian/models.py | 143 ++++++---------------------------- pygitguardian/models_utils.py | 115 +++++++++++++++++++++++++++ tests/test_client.py | 2 +- 4 files changed, 140 insertions(+), 122 deletions(-) create mode 100644 pygitguardian/models_utils.py diff --git a/pygitguardian/client.py b/pygitguardian/client.py index e192b2ff..d51ef707 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -32,7 +32,6 @@ CreateTeamInvitation, CreateTeamMember, CreateTeamMemberParameter, - CursorPaginatedResponse, DeleteMember, Detail, Document, @@ -66,6 +65,7 @@ UpdateTeam, UpdateTeamSource, ) +from .models_utils import CursorPaginatedResponse from .sca_models import ( ComputeSCAFilesResult, SCAScanAllOutput, diff --git a/pygitguardian/models.py b/pygitguardian/models.py index a2017367..0c81a5f7 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -4,25 +4,11 @@ from dataclasses import dataclass, field from datetime import date, datetime from enum import Enum -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - Generic, - List, - Literal, - Optional, - Type, - TypeVar, - cast, -) +from typing import Any, Dict, List, Literal, Optional, Type, cast from uuid import UUID import marshmallow_dataclass from marshmallow import ( - EXCLUDE, - Schema, ValidationError, fields, post_dump, @@ -30,7 +16,6 @@ pre_load, validate, ) -from typing_extensions import Self from .config import ( DEFAULT_PRE_COMMIT_MESSAGE, @@ -39,64 +24,14 @@ DOCUMENT_SIZE_THRESHOLD_BYTES, MULTI_DOCUMENT_LIMIT, ) - - -if TYPE_CHECKING: - import requests - - -class ToDictMixin: - """ - Provides a type-safe `to_dict()` method for classes using Marshmallow - """ - - SCHEMA: ClassVar[Schema] - - def to_dict(self) -> Dict[str, Any]: - return cast(Dict[str, Any], self.SCHEMA.dump(self)) - - -class FromDictMixin: - """This class must be used as an additional base class for all classes whose schema - implements a `post_load` function turning the received dict into a class instance. - - It makes it possible to deserialize an object using `MyClass.from_dict(dct)` instead - of `MyClass.SCHEMA.load(dct)`. The `from_dict()` method is shorter, but more - importantly, type-safe: its return type is an instance of `MyClass`, not - `list[Any] | Any`. - - Reference: https://marshmallow.readthedocs.io/en/stable/quickstart.html#deserializing-to-objects E501 - """ - - SCHEMA: ClassVar[Schema] - - @classmethod - def from_dict(cls, dct: Dict[str, Any]) -> Self: - return cast(Self, cls.SCHEMA.load(dct)) - - -class BaseSchema(Schema): - class Meta: - ordered = True - unknown = EXCLUDE - - -class Base(ToDictMixin): - def __init__(self, status_code: Optional[int] = None) -> None: - self.status_code = status_code - - def to_json(self) -> str: - """ - to_json converts model to JSON string. - """ - return cast(str, self.SCHEMA.dumps(self)) - - @property - def success(self) -> bool: - return self.__bool__() - - def __bool__(self) -> bool: - return self.status_code == 200 +from .models_utils import ( + Base, + BaseSchema, + FromDictMixin, + PaginationParameter, + SearchParameter, + ToDictMixin, +) class DocumentSchema(BaseSchema): @@ -1148,44 +1083,6 @@ class AccessLevel(str, Enum): RESTRICTED = "restricted" -class PaginationParameter(ToDictMixin): - """Pagination mixin used for endpoints that support pagination.""" - - cursor: str = "" - per_page: int = 20 - - -class SearchParameter(ToDictMixin): - search: Optional[str] = None - - -PaginatedData = TypeVar("PaginatedData", bound=FromDictMixin) - - -@dataclass -class CursorPaginatedResponse(Generic[PaginatedData]): - status_code: int - data: List[PaginatedData] - prev: Optional[str] = None - next: Optional[str] = None - - @classmethod - def from_response( - cls, response: "requests.Response", data_type: Type[PaginatedData] - ) -> "CursorPaginatedResponse[PaginatedData]": - data = cast( - List[PaginatedData], [data_type.from_dict(obj) for obj in response.json()] - ) - paginated_response = cls(status_code=response.status_code, data=data) - - if previous_page := response.links.get("prev"): - paginated_response.prev = previous_page["url"] - if next_page := response.links.get("next"): - paginated_response.prev = next_page["url"] - - return paginated_response - - @dataclass class MembersParameters(PaginationParameter, SearchParameter, ToDictMixin): """ @@ -1228,6 +1125,11 @@ class Member(Base, FromDictMixin): class MemberSchema(BaseSchema): + """ + This schema cannot be done through marshmallow_dataclass as we want to use the + values of the AccessLevel enum to create the enum field + """ + id = fields.Int(required=True) access_level = fields.Enum(AccessLevel, by_value=True, required=True) email = fields.Str(required=True) @@ -1249,6 +1151,11 @@ def return_member( class UpdateMemberSchema(BaseSchema): + """ + This schema cannot be done through marshmallow_dataclass as we want to use the + values of the AccessLevel enum to create the enum field + """ + id = fields.Int(required=True) access_level = fields.Enum(AccessLevel, by_value=True, allow_none=True) active = fields.Bool(allow_none=True) @@ -1323,14 +1230,10 @@ class CreateTeam(Base, FromDictMixin): description: Optional[str] = "" -class CreateTeamSchema(BaseSchema): - many = False - - name = fields.Str(required=True) - description = fields.Str(allow_none=True) - - class Meta: - exclude_none = True +CreateTeamSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(CreateTeam, base_schema=BaseSchema), +) CreateTeam.SCHEMA = CreateTeamSchema() diff --git a/pygitguardian/models_utils.py b/pygitguardian/models_utils.py new file mode 100644 index 00000000..6706a9a4 --- /dev/null +++ b/pygitguardian/models_utils.py @@ -0,0 +1,115 @@ +# pyright: reportIncompatibleVariableOverride=false +# Disable this check because of multiple non-dangerous violations (SCHEMA variables, +# BaseSchema.Meta class) +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Dict, + Generic, + List, + Optional, + Type, + TypeVar, + cast, +) + +from marshmallow import EXCLUDE, Schema +from typing_extensions import Self + + +if TYPE_CHECKING: + import requests + + +class ToDictMixin: + """ + Provides a type-safe `to_dict()` method for classes using Marshmallow + """ + + SCHEMA: ClassVar[Schema] + + def to_dict(self) -> Dict[str, Any]: + return cast(Dict[str, Any], self.SCHEMA.dump(self)) + + +class FromDictMixin: + """This class must be used as an additional base class for all classes whose schema + implements a `post_load` function turning the received dict into a class instance. + + It makes it possible to deserialize an object using `MyClass.from_dict(dct)` instead + of `MyClass.SCHEMA.load(dct)`. The `from_dict()` method is shorter, but more + importantly, type-safe: its return type is an instance of `MyClass`, not + `list[Any] | Any`. + + Reference: https://marshmallow.readthedocs.io/en/stable/quickstart.html#deserializing-to-objects E501 + """ + + SCHEMA: ClassVar[Schema] + + @classmethod + def from_dict(cls, dct: Dict[str, Any]) -> Self: + return cast(Self, cls.SCHEMA.load(dct)) + + +class BaseSchema(Schema): + class Meta: + ordered = True + unknown = EXCLUDE + + +class Base(ToDictMixin): + def __init__(self, status_code: Optional[int] = None) -> None: + self.status_code = status_code + + def to_json(self) -> str: + """ + to_json converts model to JSON string. + """ + return cast(str, self.SCHEMA.dumps(self)) + + @property + def success(self) -> bool: + return self.__bool__() + + def __bool__(self) -> bool: + return self.status_code == 200 + + +class PaginationParameter(ToDictMixin): + """Pagination mixin used for endpoints that support pagination.""" + + cursor: str = "" + per_page: int = 20 + + +class SearchParameter(ToDictMixin): + search: Optional[str] = None + + +PaginatedData = TypeVar("PaginatedData", bound=FromDictMixin) + + +@dataclass +class CursorPaginatedResponse(Generic[PaginatedData]): + status_code: int + data: List[PaginatedData] + prev: Optional[str] = None + next: Optional[str] = None + + @classmethod + def from_response( + cls, response: "requests.Response", data_type: Type[PaginatedData] + ) -> "CursorPaginatedResponse[PaginatedData]": + data = cast( + List[PaginatedData], [data_type.from_dict(obj) for obj in response.json()] + ) + paginated_response = cls(status_code=response.status_code, data=data) + + if previous_page := response.links.get("prev"): + paginated_response.prev = previous_page["url"] + if next_page := response.links.get("next"): + paginated_response.prev = next_page["url"] + + return paginated_response diff --git a/tests/test_client.py b/tests/test_client.py index 08d95f29..7b5234e3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -31,7 +31,6 @@ CreateTeamInvitation, CreateTeamMember, CreateTeamMemberParameter, - CursorPaginatedResponse, DeleteMember, Detail, HoneytokenResponse, @@ -58,6 +57,7 @@ UpdateTeam, UpdateTeamSource, ) +from pygitguardian.models_utils import CursorPaginatedResponse from pygitguardian.sca_models import ( ComputeSCAFilesResult, SCAScanAllOutput, From 463062c2bac6c7f4e8a5e36c593466fb525fffa1 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Fri, 13 Dec 2024 14:07:06 +0100 Subject: [PATCH 12/20] test(client): change client tests to allow tests to run on any workspace --- pygitguardian/client.py | 7 +- pygitguardian/models.py | 22 +- pygitguardian/models_utils.py | 2 +- tests/cassettes/test_add_team_sources.yaml | 151 +++++++++++- tests/cassettes/test_create_team.yaml | 12 +- .../test_create_team_invitation.yaml | 129 +++++++++- tests/cassettes/test_create_team_member.yaml | 186 ++++++++++++++- .../test_create_team_member_parameters.yaml | 186 ++++++++++++++- tests/cassettes/test_delete_invitation.yaml | 62 ++++- tests/cassettes/test_delete_member.yaml | 64 ++++- tests/cassettes/test_delete_team.yaml | 67 +++++- .../test_delete_team_invitation.yaml | 121 +++++++++- tests/cassettes/test_delete_team_member.yaml | 180 +++++++++++++- tests/cassettes/test_delete_team_sources.yaml | 145 +++++++++++- tests/cassettes/test_get_team.yaml | 70 +++++- tests/cassettes/test_global_team.yaml | 4 +- tests/cassettes/test_list_invitations.yaml | 8 +- tests/cassettes/test_list_members.yaml | 17 +- .../test_list_members_parameters.yaml | 4 +- tests/cassettes/test_list_sources.yaml | 26 +- .../cassettes/test_list_team_invitations.yaml | 69 +++++- tests/cassettes/test_list_team_members.yaml | 69 +++++- tests/cassettes/test_list_teams.yaml | 13 +- tests/cassettes/test_list_teams_sources.yaml | 67 +++++- tests/cassettes/test_search_sources.yaml | 6 +- .../test_search_team_invitations.yaml | 69 +++++- tests/cassettes/test_search_team_members.yaml | 69 +++++- .../cassettes/test_search_teams_sources.yaml | 67 +++++- tests/cassettes/test_send_invitation.yaml | 12 +- tests/cassettes/test_update_member.yaml | 68 +++++- tests/cassettes/test_update_team.yaml | 76 +++++- tests/conftest.py | 78 ++++++ tests/test_client.py | 222 +++++++++++++----- 33 files changed, 2142 insertions(+), 206 deletions(-) diff --git a/pygitguardian/client.py b/pygitguardian/client.py index d51ef707..ebed3b07 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -32,7 +32,7 @@ CreateTeamInvitation, CreateTeamMember, CreateTeamMemberParameter, - DeleteMember, + DeleteMemberParameters, Detail, Document, DocumentSchema, @@ -980,7 +980,6 @@ def update_member( obj: Union[Detail, Member] if is_ok(response): obj = Member.from_dict(response.json()) - print("Member : ", obj) else: obj = load_detail(response) @@ -989,7 +988,7 @@ def update_member( def delete_member( self, - member: DeleteMember, + member: DeleteMemberParameters, extra_headers: Optional[Dict[str, str]] = None, ) -> Optional[Detail]: member_id = member.id @@ -1300,7 +1299,7 @@ def list_invitations( obj.status_code return obj - def send_invitation( + def create_invitation( self, invitation: CreateInvitation, parameters: Optional[CreateInvitationParameter] = None, diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 0c81a5f7..4d43afb4 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -1172,7 +1172,7 @@ def access_level_value( @dataclass class UpdateMember(Base, FromDictMixin): """ - UpdateMember represnets the payload to update a member + UpdateMember represents the payload to update a member """ id: int @@ -1184,16 +1184,28 @@ class UpdateMember(Base, FromDictMixin): @dataclass -class DeleteMember(Base, FromDictMixin): +class UpdateMemberParameters(Base, FromDictMixin): + send_email: Optional[bool] = None + + +UpdateMemberParametersSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(UpdateMemberParameters, base_schema=BaseSchema), +) +UpdateMemberParameters.SCHEMA = UpdateMemberParametersSchema() + + +@dataclass +class DeleteMemberParameters(Base, FromDictMixin): id: int send_email: Optional[bool] = None -DeleteMemberSchema = cast( +DeleteMemberParametersSchema = cast( Type[BaseSchema], - marshmallow_dataclass.class_schema(DeleteMember, base_schema=BaseSchema), + marshmallow_dataclass.class_schema(DeleteMemberParameters, base_schema=BaseSchema), ) -DeleteMember.SCHEMA = DeleteMemberSchema() +DeleteMemberParameters.SCHEMA = DeleteMemberParametersSchema() @dataclass diff --git a/pygitguardian/models_utils.py b/pygitguardian/models_utils.py index 6706a9a4..edb75f00 100644 --- a/pygitguardian/models_utils.py +++ b/pygitguardian/models_utils.py @@ -110,6 +110,6 @@ def from_response( if previous_page := response.links.get("prev"): paginated_response.prev = previous_page["url"] if next_page := response.links.get("next"): - paginated_response.prev = next_page["url"] + paginated_response.next = next_page["url"] return paginated_response diff --git a/tests/cassettes/test_add_team_sources.yaml b/tests/cassettes/test_add_team_sources.yaml index 5278f8b1..f1d83e75 100644 --- a/tests/cassettes/test_add_team_sources.yaml +++ b/tests/cassettes/test_add_team_sources.yaml @@ -1,6 +1,141 @@ interactions: - request: - body: '{"sources_to_add": [126], "sources_to_remove": []}' + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:33 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 58fa21985f6a41e52817d0eace32cfc5 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/sources + response: + body: + string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":115,"closed_incidents_count":1,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":115,"severity_breakdown":{"critical":0,"high":1,"medium":0,"low":0,"info":0,"unknown":114}},"closed_secret_incidents":{"total":1,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":1}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"},{"id":130,"type":"jira_cloud","full_name":"yanne-gg-integration + / Mouse 804250","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10240","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/MOUS804250"},{"id":131,"type":"jira_cloud","full_name":"yanne-gg-integration + / Cheese 609495","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10241","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/CHEE609495"},{"id":132,"type":"jira_cloud","full_name":"yanne-gg-integration + / Fantastic Frozen Bacon 413763","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10242","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/FANT413763"},{"id":133,"type":"jira_cloud","full_name":"yanne-gg-integration + / Awesome Cotton Salad 575805","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10243","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/AWES575805"},{"id":134,"type":"jira_cloud","full_name":"yanne-gg-integration + / Incredible Steel Hat 449092","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10244","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/INCR449092"},{"id":135,"type":"jira_cloud","full_name":"yanne-gg-integration + / Shoes 435418","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10245","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/SHOE435418"},{"id":136,"type":"jira_cloud","full_name":"yanne-gg-integration + / Steel Ball 217001","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10246","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/STEE217001"},{"id":137,"type":"jira_cloud","full_name":"yanne-gg-integration + / Table 485589","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10247","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/TABL485589"},{"id":138,"type":"jira_cloud","full_name":"yanne-gg-integration + / Sleek Pizza 418589","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10248","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/SLEE418589"},{"id":139,"type":"jira_cloud","full_name":"yanne-gg-integration + / Pants 160878","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10249","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/PANT160878"},{"id":140,"type":"jira_cloud","full_name":"yanne-gg-integration + / Concrete Car 970994","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10250","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/CONC970994"},{"id":141,"type":"jira_cloud","full_name":"yanne-gg-integration + / Pants 965973","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10251","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/PANT965973"},{"id":142,"type":"jira_cloud","full_name":"yanne-gg-integration + / Wooden Ball 683600","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10252","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/WOOD683600"},{"id":143,"type":"jira_cloud","full_name":"yanne-gg-integration + / Incredible Steel Pizza 509982","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10253","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/INCR509982"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '14417' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:33 GMT + Link: + - ; rel="next" + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 25509b031d7733a791a772c11c7483b3 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: '{"sources_to_add": [124], "sources_to_remove": []}' headers: Accept: - '*/*' @@ -15,7 +150,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: POST - uri: https://api.gitguardian.com/v1/teams/9/sources + uri: https://api.gitguardian.com/v1/teams/19/sources response: body: string: '' @@ -31,7 +166,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 15:56:34 GMT + - Thu, 12 Dec 2024 16:59:33 GMT Referrer-Policy: - same-origin Server: @@ -45,7 +180,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 22643c94715fcf85df077d4b68ab6541 + - 74153e49ee0a825ae6f02b0784058380 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: @@ -65,7 +200,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/sources?type=azure_devops + uri: https://api.gitguardian.com/v1/teams/19/sources?type=azure_devops response: body: string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / @@ -73,7 +208,7 @@ interactions: / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test - / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":115,"closed_incidents_count":1,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":115,"severity_breakdown":{"critical":0,"high":1,"medium":0,"low":0,"info":0,"unknown":114}},"closed_secret_incidents":{"total":1,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":1}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' headers: Access-Control-Expose-Headers: @@ -89,7 +224,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 15:56:34 GMT + - Thu, 12 Dec 2024 16:59:34 GMT Link: - '' Referrer-Policy: @@ -107,7 +242,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 69887de8037bf0eba9dadb62171e5889 + - d87ddc466ebff365d07d2748c378b7ad X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_create_team.yaml b/tests/cassettes/test_create_team.yaml index f7986e96..d2fe7943 100644 --- a/tests/cassettes/test_create_team.yaml +++ b/tests/cassettes/test_create_team.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: '{"name": "team1", "description": ""}' + body: '{"name": "PyGitGuardian team", "description": ""}' headers: Accept: - '*/*' @@ -9,7 +9,7 @@ interactions: Connection: - keep-alive Content-Length: - - '36' + - '49' Content-Type: - application/json User-Agent: @@ -18,7 +18,7 @@ interactions: uri: https://api.gitguardian.com/v1/teams response: body: - string: '{"id":8,"is_global":false,"name":"team1","description":"","gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/8"}' + string: '{"id":21,"is_global":false,"name":"PyGitGuardian team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}' headers: Access-Control-Expose-Headers: - X-App-Version @@ -27,13 +27,13 @@ interactions: Connection: - keep-alive Content-Length: - - '134' + - '149' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 16:36:11 GMT + - Thu, 12 Dec 2024 16:59:31 GMT Referrer-Policy: - same-origin Server: @@ -47,7 +47,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 654ead8d7d6e98623b3b5fec06ec2a62 + - 0d603848ed5811425fb5384e2c165bf6 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_create_team_invitation.yaml b/tests/cassettes/test_create_team_invitation.yaml index bf1821de..87a8340a 100644 --- a/tests/cassettes/test_create_team_invitation.yaml +++ b/tests/cassettes/test_create_team_invitation.yaml @@ -1,6 +1,121 @@ interactions: - request: - body: '{"invitation_id": 1, "is_team_leader": true, "incident_permission": "can_view"}' + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - c2b0a05b57e590cfaf8f9fe2221eda91 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/invitations + response: + body: + string: '[{"id":13,"date":"2024-12-12T16:53:59.247129Z","email":"pygitguardian@example.com","role":"member","access_level":"member"},{"id":14,"date":"2024-12-12T16:54:44.192249Z","email":"example@test.com","role":"member","access_level":"member"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - df09bccf6aea67a2215c5dd905f6033e + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: '{"invitation_id": 13, "is_team_leader": true, "incident_permission": "can_view"}' headers: Accept: - '*/*' @@ -9,16 +124,16 @@ interactions: Connection: - keep-alive Content-Length: - - '79' + - '80' Content-Type: - application/json User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: POST - uri: https://api.gitguardian.com/v1/teams/9/team_invitations + uri: https://api.gitguardian.com/v1/teams/19/team_invitations response: body: - string: '{"id":1,"team_id":9,"invitation_id":1,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"can_view"}' + string: '{"id":7,"team_id":19,"invitation_id":13,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"can_view"}' headers: Access-Control-Expose-Headers: - X-App-Version @@ -27,13 +142,13 @@ interactions: Connection: - keep-alive Content-Length: - - '124' + - '126' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 17:37:44 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Referrer-Policy: - same-origin Server: @@ -47,7 +162,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 9f921c4d1568b2d510214689552e8902 + - 9ce9f089220243eca87b41bc81b72b73 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_create_team_member.yaml b/tests/cassettes/test_create_team_member.yaml index 4ceed804..0b0f95fd 100644 --- a/tests/cassettes/test_create_team_member.yaml +++ b/tests/cassettes/test_create_team_member.yaml @@ -1,6 +1,180 @@ interactions: - request: - body: '{"member_id": 12, "is_team_leader": false, "incident_permission": "can_view"}' + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/members + response: + body: + string: + '[{"id":6,"role":"owner","access_level":"owner","email":"toto@gg.com","name":"toto + tata","created_at":"2019-07-15T12:14:14.245000Z","last_login":"2024-12-03T09:29:43.181169Z","active":true},{"id":17,"role":"member","access_level":"member","email":"henri.delateamsecretetducorealerting@gg.com","name":"Henri + De la team secret et du core alerting","created_at":"2024-12-12T17:07:32.636754Z","last_login":null,"active":true}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '421' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 17:08:38 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - cfa0a8bd5e7f0c1617fca33915d7861d + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 17:08:38 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 637b279a05f908b2b5fcfbf2c724113e + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/team_memberships + response: + body: + string: '[{"id":23,"team_id":19,"member_id":6,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"full_access"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '127' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 17:08:38 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 128a5c3219e7182c88b624aa99dab334 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: '{"member_id": 17, "is_team_leader": false, "incident_permission": "can_view"}' headers: Accept: - '*/*' @@ -15,10 +189,10 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: POST - uri: https://api.gitguardian.com/v1/teams/9/team_memberships + uri: https://api.gitguardian.com/v1/teams/19/team_memberships response: body: - string: '{"id":10,"team_id":9,"member_id":12,"is_team_leader":false,"team_permission":"cannot_manage","incident_permission":"can_view"}' + string: '{"id":28,"team_id":19,"member_id":17,"is_team_leader":false,"team_permission":"cannot_manage","incident_permission":"can_view"}' headers: Access-Control-Expose-Headers: - X-App-Version @@ -27,13 +201,13 @@ interactions: Connection: - keep-alive Content-Length: - - '126' + - '127' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 18:12:24 GMT + - Thu, 12 Dec 2024 17:08:39 GMT Referrer-Policy: - same-origin Server: @@ -47,7 +221,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 166834a4962e207efa93e77c4ff4516d + - a55f190008b2b60e632a4e5917daec4f X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_create_team_member_parameters.yaml b/tests/cassettes/test_create_team_member_parameters.yaml index 563972a2..ffde13a7 100644 --- a/tests/cassettes/test_create_team_member_parameters.yaml +++ b/tests/cassettes/test_create_team_member_parameters.yaml @@ -1,6 +1,180 @@ interactions: - request: - body: '{"member_id": 12, "is_team_leader": false, "incident_permission": "can_view"}' + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/members + response: + body: + string: + '[{"id":6,"role":"owner","access_level":"owner","email":"toto@gg.com","name":"toto + tata","created_at":"2019-07-15T12:14:14.245000Z","last_login":"2024-12-03T09:29:43.181169Z","active":true},{"id":17,"role":"member","access_level":"member","email":"henri.delateamsecretetducorealerting@gg.com","name":"Henri + De la team secret et du core alerting","created_at":"2024-12-12T17:07:32.636754Z","last_login":null,"active":true}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '421' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 17:10:56 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 606d6020da9ea30a7136283f62e2821e + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 17:10:56 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - a19294f8bdcc49eb16a2f7156c6e77cf + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/team_memberships + response: + body: + string: '[{"id":23,"team_id":19,"member_id":6,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"full_access"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '127' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 17:10:56 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 030f95424eca92846765743b6e814339 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: '{"member_id": 17, "is_team_leader": false, "incident_permission": "can_view"}' headers: Accept: - '*/*' @@ -15,10 +189,10 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: POST - uri: https://api.gitguardian.com/v1/teams/9/team_memberships?send_email=False + uri: https://api.gitguardian.com/v1/teams/19/team_memberships?send_email=False response: body: - string: '{"id":11,"team_id":9,"member_id":12,"is_team_leader":false,"team_permission":"cannot_manage","incident_permission":"can_view"}' + string: '{"id":29,"team_id":19,"member_id":17,"is_team_leader":false,"team_permission":"cannot_manage","incident_permission":"can_view"}' headers: Access-Control-Expose-Headers: - X-App-Version @@ -27,13 +201,13 @@ interactions: Connection: - keep-alive Content-Length: - - '126' + - '127' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 14:37:57 GMT + - Thu, 12 Dec 2024 17:10:56 GMT Referrer-Policy: - same-origin Server: @@ -47,7 +221,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 9bdfa17e97c34bd4496abfdc27eb8553 + - aeeedf21c851edd2d8796d2c20e06d00 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_delete_invitation.yaml b/tests/cassettes/test_delete_invitation.yaml index 004db69a..b0b6e535 100644 --- a/tests/cassettes/test_delete_invitation.yaml +++ b/tests/cassettes/test_delete_invitation.yaml @@ -1,4 +1,60 @@ interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/invitations + response: + body: + string: '[{"id":13,"date":"2024-12-12T16:53:59.247129Z","email":"pygitguardian@example.com","role":"member","access_level":"member"},{"id":14,"date":"2024-12-12T16:54:44.192249Z","email":"example@test.com","role":"member","access_level":"member"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:34 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 308ee83bf3b6d61edde508ea06430075 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK - request: body: null headers: @@ -13,7 +69,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: DELETE - uri: https://api.gitguardian.com/v1/invitations/2 + uri: https://api.gitguardian.com/v1/invitations/13 response: body: string: '' @@ -29,7 +85,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 13:38:57 GMT + - Thu, 12 Dec 2024 16:59:34 GMT Referrer-Policy: - same-origin Server: @@ -43,7 +99,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - fd715a797aabb2eddf49823db3e54209 + - f5eb9d8b1d00b83701a8d41c427be20e X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_delete_member.yaml b/tests/cassettes/test_delete_member.yaml index 81b3d8ab..b755a070 100644 --- a/tests/cassettes/test_delete_member.yaml +++ b/tests/cassettes/test_delete_member.yaml @@ -1,4 +1,62 @@ interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/members?access_level=member + response: + body: + string: + '[{"id":16,"role":"member","access_level":"member","email":"henri.delateamsecretetducorealerting@gg.com","name":"Henri + De la team secret et du core alerting","created_at":"2024-12-12T16:54:17.568327Z","last_login":null,"active":true}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '233' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:31 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - c56b4315637313b3d5eb7e70f9439a2c + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK - request: body: null headers: @@ -13,7 +71,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: DELETE - uri: https://api.gitguardian.com/v1/members/11 + uri: https://api.gitguardian.com/v1/members/16 response: body: string: '' @@ -29,7 +87,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 16:02:49 GMT + - Thu, 12 Dec 2024 16:59:31 GMT Referrer-Policy: - same-origin Server: @@ -43,7 +101,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 6471d63d5fe47af9711f900e0911ca4b + - 2b154a78777604a093040ddbcdc76217 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_delete_team.yaml b/tests/cassettes/test_delete_team.yaml index fe8e173c..6e53c4f0 100644 --- a/tests/cassettes/test_delete_team.yaml +++ b/tests/cassettes/test_delete_team.yaml @@ -1,4 +1,65 @@ interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":18,"is_global":false,"name":"New PyGitGuardian team","description":"New + description","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/18"},{"id":19,"is_global":false,"name":"This + is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '607' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 2432b25fd8aa06e87453615a35e60888 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK - request: body: null headers: @@ -13,7 +74,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: DELETE - uri: https://api.gitguardian.com/v1/teams/8 + uri: https://api.gitguardian.com/v1/teams/18 response: body: string: '' @@ -29,7 +90,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 17:00:40 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Referrer-Policy: - same-origin Server: @@ -43,7 +104,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 0ba2e8d90bdab6ce346eb29288084b58 + - 0e6123e4cdb31c0088a084e7a394c62b X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_delete_team_invitation.yaml b/tests/cassettes/test_delete_team_invitation.yaml index f5a3b324..c2e2e35f 100644 --- a/tests/cassettes/test_delete_team_invitation.yaml +++ b/tests/cassettes/test_delete_team_invitation.yaml @@ -1,4 +1,119 @@ interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 69f3e07f70508d42fcda772509e25c8d + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/team_invitations + response: + body: + string: '[{"id":7,"team_id":19,"invitation_id":13,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"can_view"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - ab97c68068c701932313f58691187827 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK - request: body: null headers: @@ -13,7 +128,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: DELETE - uri: https://api.gitguardian.com/v1/teams/9/team_invitations/1 + uri: https://api.gitguardian.com/v1/teams/19/team_invitations/7 response: body: string: '' @@ -29,7 +144,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 17:40:00 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Referrer-Policy: - same-origin Server: @@ -43,7 +158,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 4d327fbe3c97806c8c422c4faf5a5851 + - 6328f39a8af957cbdfe428bf3becc33c X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_delete_team_member.yaml b/tests/cassettes/test_delete_team_member.yaml index 661c0b8f..1ffacb98 100644 --- a/tests/cassettes/test_delete_team_member.yaml +++ b/tests/cassettes/test_delete_team_member.yaml @@ -1,4 +1,178 @@ interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/members + response: + body: + string: + '[{"id":6,"role":"owner","access_level":"owner","email":"toto@gg.com","name":"toto + tata","created_at":"2019-07-15T12:14:14.245000Z","last_login":"2024-12-03T09:29:43.181169Z","active":true},{"id":17,"role":"member","access_level":"member","email":"henri.delateamsecretetducorealerting@gg.com","name":"Henri + De la team secret et du core alerting","created_at":"2024-12-12T17:07:32.636754Z","last_login":null,"active":false}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '422' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 13 Dec 2024 10:30:11 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 9277bcebb2641d61bcd6564b9ee1a58b + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 13 Dec 2024 10:30:11 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 5392863a13081e0dbe0e8f45ccce6ee6 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/team_memberships?is_team_leader=False + response: + body: + string: '[{"id":29,"team_id":19,"member_id":17,"is_team_leader":false,"team_permission":"cannot_manage","incident_permission":"can_view"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '129' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Fri, 13 Dec 2024 10:30:11 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - d01a1248a4b24bab5e57d86a128dead0 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK - request: body: null headers: @@ -13,7 +187,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: DELETE - uri: https://api.gitguardian.com/v1/teams/9/team_memberships/10 + uri: https://api.gitguardian.com/v1/teams/19/team_memberships/29 response: body: string: '' @@ -29,7 +203,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 18:14:25 GMT + - Fri, 13 Dec 2024 10:30:11 GMT Referrer-Policy: - same-origin Server: @@ -43,7 +217,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 5def1c122ec3d1634d34a6541f4b6a72 + - a78ae43018e7c332f1e29eda0524d5de X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_delete_team_sources.yaml b/tests/cassettes/test_delete_team_sources.yaml index 56cf1f60..e5ba31a0 100644 --- a/tests/cassettes/test_delete_team_sources.yaml +++ b/tests/cassettes/test_delete_team_sources.yaml @@ -1,6 +1,127 @@ interactions: - request: - body: '{"sources_to_add": [], "sources_to_remove": [126]}' + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:33 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - ea889edda539c08bb70d35eb8a240e7b + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/sources + response: + body: + string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":115,"closed_incidents_count":1,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":115,"severity_breakdown":{"critical":0,"high":1,"medium":0,"low":0,"info":0,"unknown":114}},"closed_secret_incidents":{"total":1,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":1}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '5158' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:33 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - cb0318be8ec3c9abb3c2decd4a2fe968 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: '{"sources_to_add": [], "sources_to_remove": [124]}' headers: Accept: - '*/*' @@ -15,7 +136,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: POST - uri: https://api.gitguardian.com/v1/teams/9/sources + uri: https://api.gitguardian.com/v1/teams/19/sources response: body: string: '' @@ -31,7 +152,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 15:53:15 GMT + - Thu, 12 Dec 2024 16:59:33 GMT Referrer-Policy: - same-origin Server: @@ -45,7 +166,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - c40c97ffa8505619495c4656de9772e8 + - 33fae4e257cf966d4a84e57269819a3e X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: @@ -65,14 +186,14 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/sources?type=azure_devops + uri: https://api.gitguardian.com/v1/teams/19/sources response: body: - string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / - gg-test / default_branch","health":"at_risk","source_criticality":"unknown","default_branch":"test","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346395Z","status":"finished","failing_reason":"","commits_scanned":23,"duration":"0.437131","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"05b69081-f346-4022-8784-198f50aed182","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/default_branch"},{"id":125,"type":"azure_devops","full_name":"gg-integration-test - / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test + string: '[{"id":125,"type":"azure_devops","full_name":"gg-integration-test / + gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test - / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":115,"closed_incidents_count":1,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":115,"severity_breakdown":{"critical":0,"high":1,"medium":0,"low":0,"info":0,"unknown":114}},"closed_secret_incidents":{"total":1,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":1}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' headers: Access-Control-Expose-Headers: @@ -82,13 +203,13 @@ interactions: Connection: - keep-alive Content-Length: - - '4304' + - '4292' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 15:53:15 GMT + - Thu, 12 Dec 2024 16:59:33 GMT Link: - '' Referrer-Policy: @@ -106,7 +227,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - fdcab3ffe17d5cc19ce6471a37f3ac25 + - baa5cf1b0ac0cfac6d146990dbd457a3 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_get_team.yaml b/tests/cassettes/test_get_team.yaml index 2f0dcd06..29a21ef3 100644 --- a/tests/cassettes/test_get_team.yaml +++ b/tests/cassettes/test_get_team.yaml @@ -11,10 +11,70 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/8 + uri: https://api.gitguardian.com/v1/teams?is_global=False response: body: - string: '{"id":8,"is_global":false,"name":"team1","description":"New description","gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/8"}' + string: + '[{"id":18,"is_global":false,"name":"Another test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/18"},{"id":19,"is_global":false,"name":"This + is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '582' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:31 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 46866d5aa60198b84fb637afe392cc0c + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/18 + response: + body: + string: '{"id":18,"is_global":false,"name":"Another test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/18"}' headers: Access-Control-Expose-Headers: - X-App-Version @@ -23,13 +83,13 @@ interactions: Connection: - keep-alive Content-Length: - - '149' + - '143' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 16:52:50 GMT + - Thu, 12 Dec 2024 16:59:31 GMT Referrer-Policy: - same-origin Server: @@ -43,7 +103,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - fd2418e3d07c887e357746bb3e06f1bc + - a8a5dd77ff9c16177901fbed8e52f67e X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_global_team.yaml b/tests/cassettes/test_global_team.yaml index dad046bd..51e8085d 100644 --- a/tests/cassettes/test_global_team.yaml +++ b/tests/cassettes/test_global_team.yaml @@ -29,7 +29,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 14:26:03 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Link: - '' Referrer-Policy: @@ -47,7 +47,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 4f67e29113b6a59a311497ba05953fda + - e6bf3c868e36b66e05d2830d35d66b6f X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_list_invitations.yaml b/tests/cassettes/test_list_invitations.yaml index 140008d4..520bd4d3 100644 --- a/tests/cassettes/test_list_invitations.yaml +++ b/tests/cassettes/test_list_invitations.yaml @@ -14,7 +14,7 @@ interactions: uri: https://api.gitguardian.com/v1/invitations response: body: - string: '[{"id":1,"date":"2024-12-05T17:35:12.273332Z","email":"toto+test@gg.com","role":"member","access_level":"member"}]' + string: '[{"id":13,"date":"2024-12-12T16:53:59.247129Z","email":"pygitguardian@example.com","role":"member","access_level":"member"},{"id":14,"date":"2024-12-12T16:54:44.192249Z","email":"example@test.com","role":"member","access_level":"member"}]' headers: Access-Control-Expose-Headers: - X-App-Version @@ -23,13 +23,13 @@ interactions: Connection: - keep-alive Content-Length: - - '114' + - '238' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 13:33:31 GMT + - Thu, 12 Dec 2024 16:59:34 GMT Link: - '' Referrer-Policy: @@ -47,7 +47,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 182ad8123736b9315def7a957569ef89 + - f762eb19d4027c92b2a3bce21967a08e X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_list_members.yaml b/tests/cassettes/test_list_members.yaml index 0306ff33..1f6f2309 100644 --- a/tests/cassettes/test_list_members.yaml +++ b/tests/cassettes/test_list_members.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: "0\r\n\r\n" + body: null headers: Accept: - '*/*' @@ -8,10 +8,6 @@ interactions: - gzip, deflate Connection: - keep-alive - Content-Type: - - application/x-www-form-urlencoded - Transfer-Encoding: - - chunked User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET @@ -19,8 +15,9 @@ interactions: response: body: string: - '[{"id":6,"role":"manager","access_level":"manager","email":"toto@gg.com","name":"toto - tata","created_at":"2019-07-15T12:14:14.245000Z","last_login":"2024-12-03T09:29:43.181169Z","active":true}]' + '[{"id":6,"role":"owner","access_level":"owner","email":"toto@gg.com","name":"toto + tata","created_at":"2019-07-15T12:14:14.245000Z","last_login":"2024-12-03T09:29:43.181169Z","active":true},{"id":16,"role":"member","access_level":"member","email":"henri.delateamsecretetducorealerting@gg.com","name":"Henri + De la team secret et du core alerting","created_at":"2024-12-12T16:54:17.568327Z","last_login":null,"active":true}]' headers: Access-Control-Expose-Headers: - X-App-Version @@ -29,13 +26,13 @@ interactions: Connection: - keep-alive Content-Length: - - '193' + - '421' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 14:27:23 GMT + - Thu, 12 Dec 2024 16:59:31 GMT Link: - '' Referrer-Policy: @@ -53,7 +50,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 218dc7bba82abc6b71f3df240dfb1536 + - fd213ee6c290b4ccfd8d3dc720260cc7 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_list_members_parameters.yaml b/tests/cassettes/test_list_members_parameters.yaml index ec06dfe1..6930726f 100644 --- a/tests/cassettes/test_list_members_parameters.yaml +++ b/tests/cassettes/test_list_members_parameters.yaml @@ -29,7 +29,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 13:53:52 GMT + - Thu, 12 Dec 2024 16:59:31 GMT Link: - '' Referrer-Policy: @@ -47,7 +47,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 345625b0c1e6c814a9b997bd7ea3ea96 + - 7bd603369ef14f95121ce24ee32fc754 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_list_sources.yaml b/tests/cassettes/test_list_sources.yaml index a0b429bb..42ff9934 100644 --- a/tests/cassettes/test_list_sources.yaml +++ b/tests/cassettes/test_list_sources.yaml @@ -19,8 +19,22 @@ interactions: / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test - / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test - / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":115,"closed_incidents_count":1,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":115,"severity_breakdown":{"critical":0,"high":1,"medium":0,"low":0,"info":0,"unknown":114}},"closed_secret_incidents":{"total":1,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":1}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"},{"id":130,"type":"jira_cloud","full_name":"yanne-gg-integration + / Mouse 804250","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10240","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/MOUS804250"},{"id":131,"type":"jira_cloud","full_name":"yanne-gg-integration + / Cheese 609495","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10241","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/CHEE609495"},{"id":132,"type":"jira_cloud","full_name":"yanne-gg-integration + / Fantastic Frozen Bacon 413763","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10242","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/FANT413763"},{"id":133,"type":"jira_cloud","full_name":"yanne-gg-integration + / Awesome Cotton Salad 575805","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10243","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/AWES575805"},{"id":134,"type":"jira_cloud","full_name":"yanne-gg-integration + / Incredible Steel Hat 449092","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10244","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/INCR449092"},{"id":135,"type":"jira_cloud","full_name":"yanne-gg-integration + / Shoes 435418","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10245","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/SHOE435418"},{"id":136,"type":"jira_cloud","full_name":"yanne-gg-integration + / Steel Ball 217001","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10246","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/STEE217001"},{"id":137,"type":"jira_cloud","full_name":"yanne-gg-integration + / Table 485589","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10247","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/TABL485589"},{"id":138,"type":"jira_cloud","full_name":"yanne-gg-integration + / Sleek Pizza 418589","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10248","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/SLEE418589"},{"id":139,"type":"jira_cloud","full_name":"yanne-gg-integration + / Pants 160878","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10249","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/PANT160878"},{"id":140,"type":"jira_cloud","full_name":"yanne-gg-integration + / Concrete Car 970994","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10250","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/CONC970994"},{"id":141,"type":"jira_cloud","full_name":"yanne-gg-integration + / Pants 965973","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10251","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/PANT965973"},{"id":142,"type":"jira_cloud","full_name":"yanne-gg-integration + / Wooden Ball 683600","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10252","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/WOOD683600"},{"id":143,"type":"jira_cloud","full_name":"yanne-gg-integration + / Incredible Steel Pizza 509982","health":"unknown","source_criticality":"unknown","default_branch":null,"default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":null,"monitored":true,"visibility":"private","external_id":"10253","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://yanne-gg-integration.atlassian.net/browse/INCR509982"}]' headers: Access-Control-Expose-Headers: - X-App-Version @@ -29,15 +43,15 @@ interactions: Connection: - keep-alive Content-Length: - - '5158' + - '14417' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 12:32:40 GMT + - Thu, 12 Dec 2024 16:59:33 GMT Link: - - '' + - ; rel="next" Referrer-Policy: - same-origin Server: @@ -53,7 +67,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 36283900ade2c8f3e9a023e5c12a1f3e + - bad417e5cb8390018bf5c37c5d5c317f X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_list_team_invitations.yaml b/tests/cassettes/test_list_team_invitations.yaml index 07640c3e..b694a8f4 100644 --- a/tests/cassettes/test_list_team_invitations.yaml +++ b/tests/cassettes/test_list_team_invitations.yaml @@ -11,10 +11,13 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/team_invitations + uri: https://api.gitguardian.com/v1/teams?is_global=False response: body: - string: '[{"id":1,"team_id":9,"invitation_id":1,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"can_view"}]' + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' headers: Access-Control-Expose-Headers: - X-App-Version @@ -23,13 +26,13 @@ interactions: Connection: - keep-alive Content-Length: - - '126' + - '438' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 17:39:11 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Link: - '' Referrer-Policy: @@ -47,7 +50,63 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - bae623918f38175cfe5c3131ddbe5cc1 + - 4ce50e7f32dc7b99902b4d9788e2863b + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/team_invitations + response: + body: + string: '[{"id":7,"team_id":19,"invitation_id":13,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"can_view"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 7066f9361cee35ae5be7ba9d9f86dfba X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_list_team_members.yaml b/tests/cassettes/test_list_team_members.yaml index 099794b6..a317d595 100644 --- a/tests/cassettes/test_list_team_members.yaml +++ b/tests/cassettes/test_list_team_members.yaml @@ -11,10 +11,13 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/team_memberships + uri: https://api.gitguardian.com/v1/teams?is_global=False response: body: - string: '[{"id":9,"team_id":9,"member_id":6,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"full_access"}]' + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' headers: Access-Control-Expose-Headers: - X-App-Version @@ -23,13 +26,13 @@ interactions: Connection: - keep-alive Content-Length: - - '125' + - '438' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 18:11:57 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Link: - '' Referrer-Policy: @@ -47,7 +50,63 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 7b6dcfa6befa810f90977de45ac5ff40 + - c2885f6c4d1d7ee41ed4336eeabb0f49 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/team_memberships + response: + body: + string: '[{"id":23,"team_id":19,"member_id":6,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"full_access"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '127' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - f73b41d095b51683ece4341247ad96c8 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_list_teams.yaml b/tests/cassettes/test_list_teams.yaml index 7355662d..9347dd0c 100644 --- a/tests/cassettes/test_list_teams.yaml +++ b/tests/cassettes/test_list_teams.yaml @@ -15,8 +15,11 @@ interactions: response: body: string: - '[{"id":6,"is_global":true,"name":"All incidents","description":null,"gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/6"},{"id":8,"is_global":false,"name":"team1","description":"New - description","gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/8"}]' + '[{"id":6,"is_global":true,"name":"All incidents","description":null,"gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/6"},{"id":18,"is_global":false,"name":"New + PyGitGuardian team","description":"New description","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/18"},{"id":19,"is_global":false,"name":"This + is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' headers: Access-Control-Expose-Headers: - X-App-Version @@ -25,13 +28,13 @@ interactions: Connection: - keep-alive Content-Length: - - '295' + - '751' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 16:55:43 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Link: - '' Referrer-Policy: @@ -49,7 +52,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 27e8974fdddaaf9e841371cdac4995ee + - ae45a816ed4d7b8482480df12113661c X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_list_teams_sources.yaml b/tests/cassettes/test_list_teams_sources.yaml index 8e85e58e..63a20eb3 100644 --- a/tests/cassettes/test_list_teams_sources.yaml +++ b/tests/cassettes/test_list_teams_sources.yaml @@ -11,7 +11,66 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/sources + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:33 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - a87a3376738fec1bdc1019d2f164ccb9 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/sources response: body: string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / @@ -19,7 +78,7 @@ interactions: / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test - / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":115,"closed_incidents_count":1,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":115,"severity_breakdown":{"critical":0,"high":1,"medium":0,"low":0,"info":0,"unknown":114}},"closed_secret_incidents":{"total":1,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":1}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' headers: Access-Control-Expose-Headers: @@ -35,7 +94,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 12:34:07 GMT + - Thu, 12 Dec 2024 16:59:33 GMT Link: - '' Referrer-Policy: @@ -53,7 +112,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - a3a02fa7fceb7f9aab20ceca3b110b6f + - 3862189652edcc8c29ca4adb962264ae X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_search_sources.yaml b/tests/cassettes/test_search_sources.yaml index 6dd9119e..4de6183c 100644 --- a/tests/cassettes/test_search_sources.yaml +++ b/tests/cassettes/test_search_sources.yaml @@ -19,7 +19,7 @@ interactions: / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test - / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":115,"closed_incidents_count":1,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":115,"severity_breakdown":{"critical":0,"high":1,"medium":0,"low":0,"info":0,"unknown":114}},"closed_secret_incidents":{"total":1,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":1}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' headers: Access-Control-Expose-Headers: @@ -35,7 +35,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 14:41:54 GMT + - Thu, 12 Dec 2024 16:59:33 GMT Link: - '' Referrer-Policy: @@ -53,7 +53,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 61df3a3b0044668d310ce7357488d6d8 + - 1da577d9ba4958fc60053554538f17f3 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_search_team_invitations.yaml b/tests/cassettes/test_search_team_invitations.yaml index 77f1cbec..9ae0fb86 100644 --- a/tests/cassettes/test_search_team_invitations.yaml +++ b/tests/cassettes/test_search_team_invitations.yaml @@ -11,10 +11,13 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/team_invitations?incident_permission=can_view + uri: https://api.gitguardian.com/v1/teams?is_global=False response: body: - string: '[]' + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' headers: Access-Control-Expose-Headers: - X-App-Version @@ -23,13 +26,13 @@ interactions: Connection: - keep-alive Content-Length: - - '2' + - '438' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 14:33:04 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Link: - '' Referrer-Policy: @@ -47,7 +50,63 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - c9f8aabf9f28131533330653571aef49 + - 566dae93fc703362ffed5e7ebadf39e1 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/team_invitations?incident_permission=can_view + response: + body: + string: '[{"id":7,"team_id":19,"invitation_id":13,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"can_view"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 29f234c1d53e950611ea39d6f3ab2d4f X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_search_team_members.yaml b/tests/cassettes/test_search_team_members.yaml index be36304b..5897b68a 100644 --- a/tests/cassettes/test_search_team_members.yaml +++ b/tests/cassettes/test_search_team_members.yaml @@ -11,10 +11,13 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/team_memberships?is_team_leader=True + uri: https://api.gitguardian.com/v1/teams?is_global=False response: body: - string: '[{"id":9,"team_id":9,"member_id":6,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"full_access"}]' + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' headers: Access-Control-Expose-Headers: - X-App-Version @@ -23,13 +26,13 @@ interactions: Connection: - keep-alive Content-Length: - - '125' + - '438' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 14:34:43 GMT + - Thu, 12 Dec 2024 16:59:32 GMT Link: - '' Referrer-Policy: @@ -47,7 +50,63 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - 6218af1c35246a5a755f460d609308fd + - 39c7b8d08138e0512342855676693e4a + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/team_memberships?is_team_leader=True + response: + body: + string: '[{"id":23,"team_id":19,"member_id":6,"is_team_leader":true,"team_permission":"can_manage","incident_permission":"full_access"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '127' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:32 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 1b97816da66a643c42f4ff36035d30ac X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_search_teams_sources.yaml b/tests/cassettes/test_search_teams_sources.yaml index b8eb5127..9b972494 100644 --- a/tests/cassettes/test_search_teams_sources.yaml +++ b/tests/cassettes/test_search_teams_sources.yaml @@ -11,7 +11,66 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/9/sources?type=azure_devops + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":19,"is_global":false,"name":"This is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '438' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:33 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - c63573489c1f2b6e3d1e7aca48b2a8d2 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams/19/sources?type=azure_devops response: body: string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / @@ -19,7 +78,7 @@ interactions: / gg-test / gg-test","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":19,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346418Z","status":"finished","failing_reason":"","commits_scanned":43,"duration":"0.492489","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"7655868e-bb15-47ab-bd62-abd97212e5e8","secret_incidents_breakdown":{"open_secret_incidents":{"total":19,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":19}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/gg-test"},{"id":126,"type":"azure_devops","full_name":"gg-integration-test / gg-test / huge_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346442Z","status":"finished","failing_reason":"","commits_scanned":1007,"duration":"1.102781","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"455c4e0c-6dc3-48ce-a0e7-819d9a8d7523","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/huge_repo"},{"id":127,"type":"azure_devops","full_name":"gg-integration-test / gg-test / new_repo","health":"safe","source_criticality":"unknown","default_branch":"master","default_branch_head":null,"open_incidents_count":0,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346464Z","status":"finished","failing_reason":"","commits_scanned":1,"duration":"0.216094","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"f9b583fb-dcfd-46ec-8938-44b427d3e596","secret_incidents_breakdown":{"open_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/new_repo"},{"id":128,"type":"azure_devops","full_name":"gg-integration-test - / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":110,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":110,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":110}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test + / gg-test / t e s t i n g","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":115,"closed_incidents_count":1,"last_scan":{"date":"2024-12-06T12:27:52.346299Z","status":"finished","failing_reason":"","commits_scanned":113,"duration":"7.597231","branches_scanned":2,"progress":100},"monitored":true,"visibility":"private","external_id":"8a132329-0c77-4efc-a18a-882bda6ab28b","secret_incidents_breakdown":{"open_secret_incidents":{"total":115,"severity_breakdown":{"critical":0,"high":1,"medium":0,"low":0,"info":0,"unknown":114}},"closed_secret_incidents":{"total":1,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":1}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/t%20e%20s%20t%20i%20n%20g"},{"id":129,"type":"azure_devops","full_name":"gg-integration-test / gg-test / test abc","health":"at_risk","source_criticality":"unknown","default_branch":"main","default_branch_head":null,"open_incidents_count":28,"closed_incidents_count":0,"last_scan":{"date":"2024-12-06T12:27:52.346367Z","status":"finished","failing_reason":"","commits_scanned":55,"duration":"0.623082","branches_scanned":1,"progress":100},"monitored":true,"visibility":"private","external_id":"2aa16b64-2fb2-4639-afeb-70c0c4e8a267","secret_incidents_breakdown":{"open_secret_incidents":{"total":28,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":28}},"closed_secret_incidents":{"total":0,"severity_breakdown":{"critical":0,"high":0,"medium":0,"low":0,"info":0,"unknown":0}}},"url":"https://dev.azure.com/gg-integration-test/gg-test/_git/test%20abc"}]' headers: Access-Control-Expose-Headers: @@ -35,7 +94,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 14:40:26 GMT + - Thu, 12 Dec 2024 16:59:33 GMT Link: - '' Referrer-Policy: @@ -53,7 +112,7 @@ interactions: X-Per-Page: - '20' X-Request-ID: - - d8e9c5602fb22289ffd73e5f8d1b3eec + - 3dbb304dd6b17f4bedc9d06dc31a9a69 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_send_invitation.yaml b/tests/cassettes/test_send_invitation.yaml index f1927556..5a06abd2 100644 --- a/tests/cassettes/test_send_invitation.yaml +++ b/tests/cassettes/test_send_invitation.yaml @@ -1,6 +1,6 @@ interactions: - request: - body: '{"email": "owl@example.com", "access_level": "member"}' + body: '{"email": "pygitguardian@example.com", "access_level": "member"}' headers: Accept: - '*/*' @@ -9,7 +9,7 @@ interactions: Connection: - keep-alive Content-Length: - - '54' + - '64' Content-Type: - application/json User-Agent: @@ -18,7 +18,7 @@ interactions: uri: https://api.gitguardian.com/v1/invitations?send_email=False response: body: - string: '{"id":2,"date":"2024-12-06T13:35:36.737833Z","email":"owl@example.com","role":"member","access_level":"member"}' + string: '{"id":15,"date":"2024-12-12T17:00:27.844380Z","email":"pygitguardian@example.com","role":"member","access_level":"member"}' headers: Access-Control-Expose-Headers: - X-App-Version @@ -27,13 +27,13 @@ interactions: Connection: - keep-alive Content-Length: - - '111' + - '122' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Fri, 06 Dec 2024 13:35:36 GMT + - Thu, 12 Dec 2024 17:00:27 GMT Referrer-Policy: - same-origin Server: @@ -47,7 +47,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 2e6e4d6e2cb571015e6c9949fa6c431f + - ef94d7e9f303dfcd2caf016acde5004b X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_update_member.yaml b/tests/cassettes/test_update_member.yaml index 00b8466f..ffacb7b8 100644 --- a/tests/cassettes/test_update_member.yaml +++ b/tests/cassettes/test_update_member.yaml @@ -1,4 +1,62 @@ interactions: + - request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/members?access_level=manager + response: + body: + string: + '[{"id":17,"role":"manager","access_level":"manager","email":"henri.delateamsecretetducorealerting@gg.com","name":"Henri + De la team secret et du core alerting","created_at":"2024-12-12T17:07:32.636754Z","last_login":null,"active":true}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '235' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 17:17:38 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - a7d3115a6902e9453b34b954d81ef5f8 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK - request: body: '{"access_level": "member", "active": false}' headers: @@ -15,10 +73,12 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: PATCH - uri: https://api.gitguardian.com/v1/members/10 + uri: https://api.gitguardian.com/v1/members/17 response: body: - string: '{"id":10,"role":"member","access_level":"member","email":"owl@gg.com","name":"owl","created_at":"2024-12-05T15:02:15.901868Z","last_login":null,"active":false}' + string: + '{"id":17,"role":"member","access_level":"member","email":"henri.delateamsecretetducorealerting@gg.com","name":"Henri + De la team secret et du core alerting","created_at":"2024-12-12T17:07:32.636754Z","last_login":null,"active":false}' headers: Access-Control-Expose-Headers: - X-App-Version @@ -33,7 +93,7 @@ interactions: Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 15:15:23 GMT + - Thu, 12 Dec 2024 17:17:38 GMT Referrer-Policy: - same-origin Server: @@ -47,7 +107,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 10079088f9a934a979e4992ca9ad6730 + - a555addad8ac0fe94b6fe646ced700b4 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/cassettes/test_update_team.yaml b/tests/cassettes/test_update_team.yaml index 6fa60dee..24bf1b04 100644 --- a/tests/cassettes/test_update_team.yaml +++ b/tests/cassettes/test_update_team.yaml @@ -1,6 +1,66 @@ interactions: - request: - body: '{"name": "team1", "description": "New description"}' + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - pygitguardian/1.18.0 (Darwin;py3.11.8) + method: GET + uri: https://api.gitguardian.com/v1/teams?is_global=False + response: + body: + string: + '[{"id":18,"is_global":false,"name":"Another test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/18"},{"id":19,"is_global":false,"name":"This + is a test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/19"},{"id":20,"is_global":false,"name":"Team + test","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/20"},{"id":21,"is_global":false,"name":"PyGitGuardian + team","description":"","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/21"}]' + headers: + Access-Control-Expose-Headers: + - X-App-Version + Allow: + - GET, POST, HEAD, OPTIONS + Connection: + - keep-alive + Content-Length: + - '582' + Content-Type: + - application/json + Cross-Origin-Opener-Policy: + - same-origin + Date: + - Thu, 12 Dec 2024 16:59:31 GMT + Link: + - '' + Referrer-Policy: + - same-origin + Server: + - nginx/1.24.0 + Vary: + - Cookie + X-App-Version: + - dev + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Per-Page: + - '20' + X-Request-ID: + - 7ef813898023adf9bd42d205e8ec4428 + X-SCA-Engine-Version: + - 2.2.0 + X-Secrets-Engine-Version: + - 2.127.0 + status: + code: 200 + message: OK + - request: + body: '{"name": "New PyGitGuardian team", "description": "New description"}' headers: Accept: - '*/*' @@ -9,16 +69,18 @@ interactions: Connection: - keep-alive Content-Length: - - '51' + - '68' Content-Type: - application/json User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: PATCH - uri: https://api.gitguardian.com/v1/teams/8 + uri: https://api.gitguardian.com/v1/teams/18 response: body: - string: '{"id":8,"is_global":false,"name":"team1","description":"New description","gitguardian_url":"https://dashboard.gitguardian.com/workspace/6/settings/user/teams/8"}' + string: + '{"id":18,"is_global":false,"name":"New PyGitGuardian team","description":"New + description","gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/18"}' headers: Access-Control-Expose-Headers: - X-App-Version @@ -27,13 +89,13 @@ interactions: Connection: - keep-alive Content-Length: - - '149' + - '168' Content-Type: - application/json Cross-Origin-Opener-Policy: - same-origin Date: - - Thu, 05 Dec 2024 16:50:36 GMT + - Thu, 12 Dec 2024 16:59:31 GMT Referrer-Policy: - same-origin Server: @@ -47,7 +109,7 @@ interactions: X-Frame-Options: - DENY X-Request-ID: - - 6997dc22059d2e12bcad60a1ca76ad5f + - 5d709cb2c3be38948d71eac6b3c4e2d5 X-SCA-Engine-Version: - 2.2.0 X-Secrets-Engine-Version: diff --git a/tests/conftest.py b/tests/conftest.py index 89bfc8c2..3e134e5e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,8 @@ import vcr from pygitguardian import GGClient +from pygitguardian.models import TeamsParameter +from pygitguardian.models_utils import CursorPaginatedResponse my_vcr = vcr.VCR( @@ -29,3 +31,79 @@ def create_client(**kwargs: Any) -> GGClient: @pytest.fixture def client(): return create_client() + + +@pytest.fixture +def get_team(client: GGClient): + """ + Return a function that fetches the first team available + in the account, every account should have at least + one team (all incidents) but we skip it since we cannot + add sources to it + """ + + def inner(): + paginated_teams = client.list_teams(TeamsParameter(is_global=False)) + assert isinstance( + paginated_teams, CursorPaginatedResponse + ), "Could not fetch teams from GitGuardian" + + return paginated_teams.data[0] + + return inner + + +@pytest.fixture +def get_source(client: GGClient): + """ + Return a function that fetches the first source available + in the account, not all accounts have a source so testing + from scratch will require installing a source first + """ + + def inner(): + paginated_sources = client.list_sources() + assert isinstance( + paginated_sources, CursorPaginatedResponse + ), "Could not fetch sources from GitGuardian" + return paginated_sources.data[0] + + return inner + + +@pytest.fixture +def get_member(client: GGClient): + """ + Return a function that fetches the first member available + in the account, every account should have at least + one member (the owner) + """ + + def inner(): + paginated_teams = client.list_members() + assert isinstance( + paginated_teams, CursorPaginatedResponse + ), "Could not fetch members from GitGuardian" + + return paginated_teams.data[0] + + return inner + + +@pytest.fixture +def get_invitation(client: GGClient): + """ + Return a function that fetches the first invitation available + in the account, there is no invitation by default, one should be + created to setup the test + """ + + def inner(): + paginated_teams = client.list_invitations() + assert isinstance( + paginated_teams, CursorPaginatedResponse + ), "Could not fetch members from GitGuardian" + + return paginated_teams.data[0] + + return inner diff --git a/tests/test_client.py b/tests/test_client.py index 7b5234e3..7c60546e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,7 +4,7 @@ from collections import OrderedDict from datetime import date from io import BytesIO -from typing import Any, Dict, List, Optional, Tuple, Type +from typing import Any, Callable, Dict, List, Optional, Tuple, Type from unittest.mock import Mock, patch import pytest @@ -31,7 +31,7 @@ CreateTeamInvitation, CreateTeamMember, CreateTeamMemberParameter, - DeleteMember, + DeleteMemberParameters, Detail, HoneytokenResponse, HoneytokenWithContextResponse, @@ -1478,8 +1478,14 @@ def test_update_member(client: GGClient): THEN it returns the updated member """ + # This assumes there is at least one manager in the first page of members + members = client.list_members(MembersParameters(access_level=AccessLevel.MANAGER)) + assert isinstance(members, CursorPaginatedResponse), "Could not fetch members" + result = client.update_member( - UpdateMember(id=10, access_level=AccessLevel.MEMBER, active=False) + UpdateMember( + id=members.data[0].id, access_level=AccessLevel.MEMBER, active=False + ) ) assert isinstance(result, Member), result @@ -1496,7 +1502,11 @@ def test_delete_member(client: GGClient): THEN the member is deleted """ - result = client.delete_member(DeleteMember(id=11)) + members = client.list_members(MembersParameters(access_level=AccessLevel.MEMBER)) + assert isinstance(members, CursorPaginatedResponse), "Could not fetch members" + + member = members.data[0] + result = client.delete_member(DeleteMemberParameters(id=member.id)) assert result is None, result @@ -1509,39 +1519,45 @@ def test_create_team(client: GGClient): THEN a team is created """ - result = client.create_team(CreateTeam(name="team1")) + result = client.create_team(CreateTeam(name="PyGitGuardian team")) assert isinstance(result, Team), result @my_vcr.use_cassette("test_get_team.yaml", ignore_localhost=False) -def test_get_team(client: GGClient): +def test_get_team(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling GET /teams/{id} endpoint THEN the corresponding team is returned """ - result = client.get_team(8) + # This test would require a team to be created first and its id + # stored in a config file for this test to simulate a real use case + team = get_team() + result = client.get_team(team.id) assert isinstance(result, Team), result @my_vcr.use_cassette("test_update_team.yaml", ignore_localhost=False) -def test_update_team(client: GGClient): +def test_update_team(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling PATCH /teams endpoint THEN the corresponding team is updated """ + team = get_team() result = client.update_team( - UpdateTeam(id=8, name="team1", description="New description") + UpdateTeam( + id=team.id, name="New PyGitGuardian team", description="New description" + ) ) assert isinstance(result, Team), result - assert result.name == "team1" + assert result.name == "New PyGitGuardian team" assert result.description == "New description" @@ -1576,30 +1592,38 @@ def test_global_team(client: GGClient): @my_vcr.use_cassette("test_delete_team.yaml", ignore_localhost=False) -def test_delete_team(client: GGClient): +def test_delete_team(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling DELETE /teams/{id} endpoint THEN the team is deleted """ - result = client.delete_team(8) + team = get_team() + result = client.delete_team(team.id) assert result is None @my_vcr.use_cassette("test_create_team_invitation.yaml", ignore_localhost=False) -def test_create_team_invitation(client: GGClient): +def test_create_team_invitation( + client: GGClient, + get_team: Callable[[], Team], + get_invitation: Callable[[], Invitation], +): """ GIVEN a client WHEN calling POST /teams/{id}/invitations endpoint THEN an invitation is created """ + team = get_team() + invitation = get_invitation() + result = client.create_team_invitation( - 9, + team.id, CreateTeamInvitation( - invitation_id=1, + invitation_id=invitation.id, is_team_leader=True, incident_permission=IncidentPermission.VIEW, ), @@ -1609,21 +1633,23 @@ def test_create_team_invitation(client: GGClient): @my_vcr.use_cassette("test_list_team_invitations.yaml", ignore_localhost=False) -def test_list_team_invitations(client: GGClient): +def test_list_team_invitations(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling GET /teams/{id}/invitations endpoint THEN a paginated list of invitations is returned """ - result = client.list_team_invitations(9) + team = get_team() + result = client.list_team_invitations(team.id) assert isinstance(result, CursorPaginatedResponse), result + # This assumes there is at least one team invitation assert isinstance(result.data[0], TeamInvitation) @my_vcr.use_cassette("test_search_team_invitations.yaml", ignore_localhost=False) -def test_search_team_invitations(client: GGClient): +def test_search_team_invitations(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling GET /teams/{id}/invitations endpoint @@ -1631,8 +1657,9 @@ def test_search_team_invitations(client: GGClient): THEN a paginated list of invitations is returned matching the parameters """ + team = get_team() result = client.list_team_invitations( - 9, + team.id, parameters=TeamInvitationParameter(incident_permission=IncidentPermission.VIEW), ) @@ -1643,34 +1670,41 @@ def test_search_team_invitations(client: GGClient): @my_vcr.use_cassette("test_delete_team_invitation.yaml", ignore_localhost=False) -def test_delete_team_invitation(client: GGClient): +def test_delete_team_invitation(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling DELETE /teams/{id}/invitations/{id} endpoint THEN an invitation is deleted """ - result = client.delete_team_invitation(9, 1) + team = get_team() + team_invitations = client.list_team_invitations(team.id) + assert isinstance( + team_invitations, CursorPaginatedResponse + ), "Could not fetch team invitations" + + result = client.delete_team_invitation(team.id, team_invitations.data[0].id) assert result is None @my_vcr.use_cassette("test_list_team_members.yaml", ignore_localhost=False) -def test_list_team_members(client: GGClient): +def test_list_team_members(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling GET /teams/{id}/members endpoint THEN a paginated list of members is returned """ - result = client.list_team_members(9) + team = get_team() + result = client.list_team_members(team.id) assert isinstance(result, CursorPaginatedResponse), result assert isinstance(result.data[0], TeamMember) @my_vcr.use_cassette("test_search_team_members.yaml", ignore_localhost=False) -def test_search_team_members(client: GGClient): +def test_search_team_members(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling GET /teams/{id}/members endpoint @@ -1678,8 +1712,12 @@ def test_search_team_members(client: GGClient): THEN a paginated list of members is returned matching the parameters """ + team = get_team() + + # Every team should have at least one team leader, but an account without a team + # will nullify the purpose of this test even though it will pass result = client.list_team_members( - 9, parameters=TeamMemberParameter(is_team_leader=True) + team.id, parameters=TeamMemberParameter(is_team_leader=True) ) assert isinstance(result, CursorPaginatedResponse), result @@ -1687,15 +1725,34 @@ def test_search_team_members(client: GGClient): @my_vcr.use_cassette("test_create_team_member.yaml", ignore_localhost=False) -def test_create_team_member(client: GGClient): +def test_create_team_member(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling POST /teams/{id}/members endpoint THEN a member is created """ + + all_members = client.list_members() + assert isinstance( + all_members, CursorPaginatedResponse + ), "Could not fetch members from GitGuardian" + + team = get_team() + team_members = client.list_team_members(team.id) + assert isinstance( + team_members, CursorPaginatedResponse + ), "Could not fetch team members from GitGuardian" + team_members_ids = {team_member.member_id for team_member in team_members.data} + + # This assumes there is at least one member in the first page of team members that + # does not belong to the retrieved team + member_to_add = next( + member for member in all_members.data if member.id not in team_members_ids + ) + result = client.create_team_member( - 9, - CreateTeamMember(12, False, IncidentPermission.VIEW), + team.id, + CreateTeamMember(member_to_add.id, False, IncidentPermission.VIEW), ) assert isinstance(result, TeamMember), result @@ -1705,16 +1762,36 @@ def test_create_team_member(client: GGClient): @my_vcr.use_cassette("test_create_team_member_parameters.yaml", ignore_localhost=False) -def test_create_team_member_without_mail(client: GGClient): +def test_create_team_member_without_mail( + client: GGClient, get_team: Callable[[], Team] +): """ GIVEN a client WHEN calling POST /teams/{id}/members endpoint THEN a member is created """ + all_members = client.list_members() + assert isinstance( + all_members, CursorPaginatedResponse + ), "Could not fetch members from GitGuardian" + + team = get_team() + team_members = client.list_team_members(team.id) + assert isinstance( + team_members, CursorPaginatedResponse + ), "Could not fetch team members from GitGuardian" + team_members_ids = {team_member.member_id for team_member in team_members.data} + + # This assumes there is at least one member in the first page of team members that + # does not belong to the retrieved team + member_to_add = next( + member for member in all_members.data if member.id not in team_members_ids + ) + result = client.create_team_member( - 9, - CreateTeamMember(12, False, IncidentPermission.VIEW), + team.id, + CreateTeamMember(member_to_add.id, False, IncidentPermission.VIEW), CreateTeamMemberParameter(send_email=False), ) @@ -1722,14 +1799,28 @@ def test_create_team_member_without_mail(client: GGClient): @my_vcr.use_cassette("test_delete_team_member.yaml", ignore_localhost=False) -def test_delete_team_member(client: GGClient): +def test_delete_team_member(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling DELETE /teams/{id}/members/{id} endpoint THEN a member is deleted """ - result = client.delete_team_member(9, 10) + all_members = client.list_members() + assert isinstance( + all_members, CursorPaginatedResponse + ), "Could not fetch members from GitGuardian" + + team = get_team() + team_members = client.list_team_members( + team.id, TeamMemberParameter(is_team_leader=False) + ) + assert isinstance( + team_members, CursorPaginatedResponse + ), "Could not fetch team members from GitGuardian" + + team_member = team_members.data[0] + result = client.delete_team_member(team.id, team_member.id) assert result is None @@ -1763,20 +1854,22 @@ def test_search_sources(client: GGClient): @my_vcr.use_cassette("test_list_teams_sources.yaml", ignore_localhost=False) -def test_list_team_sources(client: GGClient): +def test_list_team_sources(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling GET /sources endpoint THEN a paginated list of sources is returned """ - result = client.list_teams_sources(9) + result = client.list_teams_sources(get_team().id) assert isinstance(result, CursorPaginatedResponse), result + + # This assumes at least one source has been installed and is on the perimeter of a team assert isinstance(result.data[0], Source) @my_vcr.use_cassette("test_search_teams_sources.yaml", ignore_localhost=False) -def test_search_team_sources(client: GGClient): +def test_search_team_sources(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling GET /sources endpoint @@ -1784,50 +1877,63 @@ def test_search_team_sources(client: GGClient): THEN a paginated list of sources is returned matching the parameters """ - result = client.list_teams_sources(9, TeamSourceParameters(type="azure_devops")) + result = client.list_teams_sources( + get_team().id, TeamSourceParameters(type="azure_devops") + ) assert isinstance(result, CursorPaginatedResponse), result assert all(source.type == "azure_devops" for source in result.data) @my_vcr.use_cassette("test_delete_team_sources.yaml", ignore_localhost=False) -def test_delete_team_sources(client: GGClient): +def test_delete_team_sources(client: GGClient, get_team: Callable[[], Team]): """ GIVEN a client WHEN calling POST /teams/{id}/sources endpoint THEN a source is deleted """ - result = client.update_team_source(UpdateTeamSource(9, [], [126])) + team = get_team() + team_sources = client.list_teams_sources(team.id) + assert isinstance( + team_sources, CursorPaginatedResponse + ), "Could not fetch team sources" + source_to_delete = team_sources.data[0] + result = client.update_team_source( + UpdateTeamSource(team.id, [], [source_to_delete.id]) + ) assert result == 204 - team_sources = client.list_teams_sources( - 9, TeamSourceParameters(type="azure_devops") - ) - assert isinstance(team_sources, CursorPaginatedResponse), team_sources.content - assert not any(source.id == 126 for source in team_sources.data) + team_sources = client.list_teams_sources(team.id) + assert isinstance(team_sources, CursorPaginatedResponse), team_sources + assert not any(source.id == source_to_delete.id for source in team_sources.data) @my_vcr.use_cassette("test_add_team_sources.yaml", ignore_localhost=False) -def test_add_team_sources(client: GGClient): +def test_add_team_sources( + client: GGClient, get_team: Callable[[], Team], get_source: Callable[[], Source] +): """ GIVEN a client WHEN calling POST /teams/{id}/sources endpoint THEN a source is added """ + team = get_team() + source = get_source() + result = client.update_team_source( - UpdateTeamSource(9, [126], []), + UpdateTeamSource(team.id, [source.id], []), ) assert result == 204 team_sources = client.list_teams_sources( - 9, TeamSourceParameters(type="azure_devops") + team.id, TeamSourceParameters(type="azure_devops") ) - assert isinstance(team_sources, CursorPaginatedResponse), team_sources.content - assert any(source.id == 126 for source in team_sources.data) + assert isinstance(team_sources, CursorPaginatedResponse), team_sources + assert any(received_source.id == source.id for received_source in team_sources.data) @my_vcr.use_cassette("test_list_invitations.yaml", ignore_localhost=False) @@ -1840,6 +1946,7 @@ def test_list_invitations(client: GGClient): result = client.list_invitations() assert isinstance(result, CursorPaginatedResponse), result + # This assumes there is at least one invitation sent in the account assert isinstance(result.data[0], Invitation) @@ -1851,14 +1958,16 @@ def test_send_invitation(client: GGClient): THEN an invitation is sent """ - result = client.send_invitation( - CreateInvitation(email="owl@example.com", access_level=AccessLevel.MEMBER), + result = client.create_invitation( + CreateInvitation( + email="pygitguardian@example.com", access_level=AccessLevel.MEMBER + ), CreateInvitationParameter(send_email=False), ) assert isinstance(result, Invitation), result - assert result.email == "owl@example.com" + assert result.email == "pygitguardian@example.com" assert result.access_level == AccessLevel.MEMBER @@ -1867,9 +1976,14 @@ def test_delete_invitation(client: GGClient): """ GIVEN a client WHEN calling DELETE /invitations/{id} endpoint - THEN an invitation is deleted + THEN the invitation is deleted """ - result = client.delete_invitation(2) + invitations = client.list_invitations() + assert isinstance( + invitations, CursorPaginatedResponse + ), "Could not fetch invitations" + + result = client.delete_invitation(invitations.data[0].id) assert result is None From 90f3f02317d9326d6fd464ee12e25e37469d5f5c Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Fri, 13 Dec 2024 14:58:38 +0100 Subject: [PATCH 13/20] refactor(client): rename list team source to better match public api doc --- pygitguardian/client.py | 2 +- tests/test_client.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pygitguardian/client.py b/pygitguardian/client.py index ebed3b07..b67688c1 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -1236,7 +1236,7 @@ def list_sources( obj.status_code return obj - def list_teams_sources( + def list_team_sources( self, team_id: int, parameters: Optional[TeamSourceParameters] = None, diff --git a/tests/test_client.py b/tests/test_client.py index 7c60546e..e649aab3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1861,7 +1861,7 @@ def test_list_team_sources(client: GGClient, get_team: Callable[[], Team]): THEN a paginated list of sources is returned """ - result = client.list_teams_sources(get_team().id) + result = client.list_team_sources(get_team().id) assert isinstance(result, CursorPaginatedResponse), result # This assumes at least one source has been installed and is on the perimeter of a team @@ -1877,7 +1877,7 @@ def test_search_team_sources(client: GGClient, get_team: Callable[[], Team]): THEN a paginated list of sources is returned matching the parameters """ - result = client.list_teams_sources( + result = client.list_team_sources( get_team().id, TeamSourceParameters(type="azure_devops") ) @@ -1894,7 +1894,7 @@ def test_delete_team_sources(client: GGClient, get_team: Callable[[], Team]): """ team = get_team() - team_sources = client.list_teams_sources(team.id) + team_sources = client.list_team_sources(team.id) assert isinstance( team_sources, CursorPaginatedResponse ), "Could not fetch team sources" @@ -1905,7 +1905,7 @@ def test_delete_team_sources(client: GGClient, get_team: Callable[[], Team]): assert result == 204 - team_sources = client.list_teams_sources(team.id) + team_sources = client.list_team_sources(team.id) assert isinstance(team_sources, CursorPaginatedResponse), team_sources assert not any(source.id == source_to_delete.id for source in team_sources.data) @@ -1929,7 +1929,7 @@ def test_add_team_sources( assert result == 204 - team_sources = client.list_teams_sources( + team_sources = client.list_team_sources( team.id, TeamSourceParameters(type="azure_devops") ) assert isinstance(team_sources, CursorPaginatedResponse), team_sources From 4a06722830cb6747a367b0a5334d6be2eab0c706 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 18 Dec 2024 15:12:59 +0100 Subject: [PATCH 14/20] refactor(models): prefix all post_load method with make --- pygitguardian/models.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 4d43afb4..89cabcbd 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -1102,7 +1102,7 @@ class MembersParametersSchema(BaseSchema): ordering = fields.Str(allow_none=True) @post_load - def return_members_parameters(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): + def make_members_parameters(self, data: Dict[str, Any], **kwargs: Any): return MembersParameters(**data) @@ -1142,7 +1142,7 @@ class MemberSchema(BaseSchema): def return_member( self, data: Dict[str, Any], - **kwargs: Dict[str, Any], + **kwargs: Any, ): return Member(**data) @@ -1161,9 +1161,7 @@ class UpdateMemberSchema(BaseSchema): active = fields.Bool(allow_none=True) @post_dump - def access_level_value( - self, data: Dict[str, Any], **kwargs: Dict[str, Any] - ) -> Dict[str, Any]: + def access_level_value(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: if "access_level" in data: data["access_level"] = AccessLevel(data["access_level"]).value return data @@ -1316,10 +1314,10 @@ class TeamInvitationSchema(BaseSchema): incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) @post_load - def return_member( + def make_team_invitation( self, data: Dict[str, Any], - **kwargs: Dict[str, Any], + **kwargs: Any, ): return TeamInvitation(**data) @@ -1342,7 +1340,7 @@ class CreateTeamInvitationSchema(BaseSchema): incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) @post_load - def return_team_invitation(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): + def make_team_invitation(self, data: Dict[str, Any], **kwargs: Any): return CreateTeamInvitation(**data) class Meta: @@ -1367,9 +1365,7 @@ class TeamMembershipParameterSchema(BaseSchema): member_id = fields.Int(allow_none=True) @post_load - def return_team_membership_parameter( - self, data: Dict[str, Any], **kwargs: Dict[str, Any] - ): + def make_team_member_parameter(self, data: Dict[str, Any], **kwargs: Any): return TeamMemberParameter(**data) class Meta: @@ -1398,7 +1394,7 @@ class TeamMemberSchema(BaseSchema): incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) @post_load - def return_team_membership(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): + def make_team_member(self, data: Dict[str, Any], **kwargs: Any): return TeamMember(**data) @@ -1434,9 +1430,7 @@ class CreateTeamMemberSchema(BaseSchema): incident_permission = fields.Enum(IncidentPermission, by_value=True, required=True) @post_load - def return_create_team_membership( - self, data: Dict[str, Any], **kwargs: Dict[str, Any] - ): + def make_create_team_member(self, data: Dict[str, Any], **kwargs: Any): return CreateTeamMember(**data) @@ -1510,7 +1504,7 @@ class InvitationSchema(BaseSchema): date = fields.DateTime(required=True) @post_load - def return_invitation(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): + def make_invitation(self, data: Dict[str, Any], **kwargs: Any): return Invitation(**data) @@ -1542,7 +1536,7 @@ class CreateInvitationSchema(BaseSchema): access_level = fields.Enum(AccessLevel, by_value=True, required=True) @post_load - def return_invitation(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): + def make_create_invitation(self, data: Dict[str, Any], **kwargs: Any): return CreateInvitation(**data) From 994f9875725037a1d24b6dc40fd09399b156d272 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 18 Dec 2024 15:40:51 +0100 Subject: [PATCH 15/20] refactor(client): rename "update" function model's argument to payload --- pygitguardian/client.py | 26 +++++++++----------------- tests/conftest.py | 19 ------------------- tests/test_client.py | 4 ++-- 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/pygitguardian/client.py b/pygitguardian/client.py index b67688c1..daf6ee66 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -1021,7 +1021,6 @@ def list_teams( else: obj = load_detail(response) - obj.status_code return obj def get_team( @@ -1063,11 +1062,11 @@ def create_team( def update_team( self, - team: UpdateTeam, + payload: UpdateTeam, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, Team]: - team_id = team.id - data = UpdateTeam.to_dict(team) + team_id = payload.id + data = UpdateTeam.to_dict(payload) del data["id"] response = self.patch( @@ -1118,7 +1117,6 @@ def list_team_invitations( else: obj = load_detail(response) - obj.status_code return obj def create_team_invitation( @@ -1176,7 +1174,6 @@ def list_team_members( else: obj = load_detail(response) - obj.status_code return obj def create_team_member( @@ -1233,7 +1230,6 @@ def list_sources( else: obj = load_detail(response) - obj.status_code return obj def list_team_sources( @@ -1254,16 +1250,15 @@ def list_team_sources( else: obj = load_detail(response) - obj.status_code return obj def update_team_source( self, - team_sources: UpdateTeamSource, + payload: UpdateTeamSource, extra_headers: Optional[Dict[str, str]] = None, - ) -> Union[Detail, int]: - team_id = team_sources.team_id - data = team_sources.to_dict() + ) -> Optional[Detail]: + team_id = payload.team_id + data = payload.to_dict() del data["team_id"] response = self.post( @@ -1272,10 +1267,8 @@ def update_team_source( extra_headers=extra_headers, ) - if response.status_code == 204: - return 204 - - return load_detail(response) + if not response.status_code == 204: + return load_detail(response) def list_invitations( self, @@ -1296,7 +1289,6 @@ def list_invitations( else: obj = load_detail(response) - obj.status_code return obj def create_invitation( diff --git a/tests/conftest.py b/tests/conftest.py index 3e134e5e..84932f82 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -71,25 +71,6 @@ def inner(): return inner -@pytest.fixture -def get_member(client: GGClient): - """ - Return a function that fetches the first member available - in the account, every account should have at least - one member (the owner) - """ - - def inner(): - paginated_teams = client.list_members() - assert isinstance( - paginated_teams, CursorPaginatedResponse - ), "Could not fetch members from GitGuardian" - - return paginated_teams.data[0] - - return inner - - @pytest.fixture def get_invitation(client: GGClient): """ diff --git a/tests/test_client.py b/tests/test_client.py index e649aab3..b841a44f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1903,7 +1903,7 @@ def test_delete_team_sources(client: GGClient, get_team: Callable[[], Team]): UpdateTeamSource(team.id, [], [source_to_delete.id]) ) - assert result == 204 + assert result is None team_sources = client.list_team_sources(team.id) assert isinstance(team_sources, CursorPaginatedResponse), team_sources @@ -1927,7 +1927,7 @@ def test_add_team_sources( UpdateTeamSource(team.id, [source.id], []), ) - assert result == 204 + assert result is None team_sources = client.list_team_sources( team.id, TeamSourceParameters(type="azure_devops") From 1ff53dceae840c6b7ba4668cbaf09aeb5da76df2 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 18 Dec 2024 15:43:11 +0100 Subject: [PATCH 16/20] docs(tests): add documentation around limitations of initial workplace setup --- doc/dev/tests.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 doc/dev/tests.md diff --git a/doc/dev/tests.md b/doc/dev/tests.md new file mode 100644 index 00000000..927f9e3a --- /dev/null +++ b/doc/dev/tests.md @@ -0,0 +1,31 @@ +# Users, teams and sources + +It is possible to removed all cassettes from this repository and run them all by providing a valid personal access token. + +However, for users, teams and sources, this requires a workplace with enough data filled in. + +## For source related tests + +We require the workplace to have: + +- At least two sources + +## For member related tests + +We require the workplace to have: + +- At least two members, they should have different access levels +- At least one invitation sent out + +## For team related tests + +We require the workplace to have : + +- At least two teams +- These teams should have 2 or more members +- These teams should have at least one monitored source in their perimeter +- ⚠️ No team should have all available sources in their perimeter +- There should exist a team where at least one member is not invited +- There should exist one pending team invitation + +> Keep in mind that some of these tests will delete resources, so they should run in an isolated workplace From 20b162401f540e7791ea4e83e43ddd8c306a4045 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Wed, 18 Dec 2024 16:36:06 +0100 Subject: [PATCH 17/20] test(client): move get functions to utils file --- tests/conftest.py | 59 -------------------------------------------- tests/test_client.py | 43 ++++++++++++++------------------ tests/utils.py | 52 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 84 deletions(-) create mode 100644 tests/utils.py diff --git a/tests/conftest.py b/tests/conftest.py index 84932f82..89bfc8c2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,8 +6,6 @@ import vcr from pygitguardian import GGClient -from pygitguardian.models import TeamsParameter -from pygitguardian.models_utils import CursorPaginatedResponse my_vcr = vcr.VCR( @@ -31,60 +29,3 @@ def create_client(**kwargs: Any) -> GGClient: @pytest.fixture def client(): return create_client() - - -@pytest.fixture -def get_team(client: GGClient): - """ - Return a function that fetches the first team available - in the account, every account should have at least - one team (all incidents) but we skip it since we cannot - add sources to it - """ - - def inner(): - paginated_teams = client.list_teams(TeamsParameter(is_global=False)) - assert isinstance( - paginated_teams, CursorPaginatedResponse - ), "Could not fetch teams from GitGuardian" - - return paginated_teams.data[0] - - return inner - - -@pytest.fixture -def get_source(client: GGClient): - """ - Return a function that fetches the first source available - in the account, not all accounts have a source so testing - from scratch will require installing a source first - """ - - def inner(): - paginated_sources = client.list_sources() - assert isinstance( - paginated_sources, CursorPaginatedResponse - ), "Could not fetch sources from GitGuardian" - return paginated_sources.data[0] - - return inner - - -@pytest.fixture -def get_invitation(client: GGClient): - """ - Return a function that fetches the first invitation available - in the account, there is no invitation by default, one should be - created to setup the test - """ - - def inner(): - paginated_teams = client.list_invitations() - assert isinstance( - paginated_teams, CursorPaginatedResponse - ), "Could not fetch members from GitGuardian" - - return paginated_teams.data[0] - - return inner diff --git a/tests/test_client.py b/tests/test_client.py index b841a44f..8600890d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,7 +4,7 @@ from collections import OrderedDict from datetime import date from io import BytesIO -from typing import Any, Callable, Dict, List, Optional, Tuple, Type +from typing import Any, Dict, List, Optional, Tuple, Type from unittest.mock import Mock, patch import pytest @@ -67,6 +67,7 @@ ) from .conftest import create_client, my_vcr +from .utils import get_invitation, get_source, get_team FILENAME = ".env" @@ -1525,7 +1526,7 @@ def test_create_team(client: GGClient): @my_vcr.use_cassette("test_get_team.yaml", ignore_localhost=False) -def test_get_team(client: GGClient, get_team: Callable[[], Team]): +def test_get_team(client: GGClient): """ GIVEN a client WHEN calling GET /teams/{id} endpoint @@ -1541,7 +1542,7 @@ def test_get_team(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_update_team.yaml", ignore_localhost=False) -def test_update_team(client: GGClient, get_team: Callable[[], Team]): +def test_update_team(client: GGClient): """ GIVEN a client WHEN calling PATCH /teams endpoint @@ -1592,7 +1593,7 @@ def test_global_team(client: GGClient): @my_vcr.use_cassette("test_delete_team.yaml", ignore_localhost=False) -def test_delete_team(client: GGClient, get_team: Callable[[], Team]): +def test_delete_team(client: GGClient): """ GIVEN a client WHEN calling DELETE /teams/{id} endpoint @@ -1606,11 +1607,7 @@ def test_delete_team(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_create_team_invitation.yaml", ignore_localhost=False) -def test_create_team_invitation( - client: GGClient, - get_team: Callable[[], Team], - get_invitation: Callable[[], Invitation], -): +def test_create_team_invitation(client: GGClient): """ GIVEN a client WHEN calling POST /teams/{id}/invitations endpoint @@ -1633,7 +1630,7 @@ def test_create_team_invitation( @my_vcr.use_cassette("test_list_team_invitations.yaml", ignore_localhost=False) -def test_list_team_invitations(client: GGClient, get_team: Callable[[], Team]): +def test_list_team_invitations(client: GGClient): """ GIVEN a client WHEN calling GET /teams/{id}/invitations endpoint @@ -1649,7 +1646,7 @@ def test_list_team_invitations(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_search_team_invitations.yaml", ignore_localhost=False) -def test_search_team_invitations(client: GGClient, get_team: Callable[[], Team]): +def test_search_team_invitations(client: GGClient): """ GIVEN a client WHEN calling GET /teams/{id}/invitations endpoint @@ -1670,7 +1667,7 @@ def test_search_team_invitations(client: GGClient, get_team: Callable[[], Team]) @my_vcr.use_cassette("test_delete_team_invitation.yaml", ignore_localhost=False) -def test_delete_team_invitation(client: GGClient, get_team: Callable[[], Team]): +def test_delete_team_invitation(client: GGClient): """ GIVEN a client WHEN calling DELETE /teams/{id}/invitations/{id} endpoint @@ -1689,7 +1686,7 @@ def test_delete_team_invitation(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_list_team_members.yaml", ignore_localhost=False) -def test_list_team_members(client: GGClient, get_team: Callable[[], Team]): +def test_list_team_members(client: GGClient): """ GIVEN a client WHEN calling GET /teams/{id}/members endpoint @@ -1704,7 +1701,7 @@ def test_list_team_members(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_search_team_members.yaml", ignore_localhost=False) -def test_search_team_members(client: GGClient, get_team: Callable[[], Team]): +def test_search_team_members(client: GGClient): """ GIVEN a client WHEN calling GET /teams/{id}/members endpoint @@ -1725,7 +1722,7 @@ def test_search_team_members(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_create_team_member.yaml", ignore_localhost=False) -def test_create_team_member(client: GGClient, get_team: Callable[[], Team]): +def test_create_team_member(client: GGClient): """ GIVEN a client WHEN calling POST /teams/{id}/members endpoint @@ -1762,9 +1759,7 @@ def test_create_team_member(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_create_team_member_parameters.yaml", ignore_localhost=False) -def test_create_team_member_without_mail( - client: GGClient, get_team: Callable[[], Team] -): +def test_create_team_member_without_mail(client: GGClient): """ GIVEN a client WHEN calling POST /teams/{id}/members endpoint @@ -1799,7 +1794,7 @@ def test_create_team_member_without_mail( @my_vcr.use_cassette("test_delete_team_member.yaml", ignore_localhost=False) -def test_delete_team_member(client: GGClient, get_team: Callable[[], Team]): +def test_delete_team_member(client: GGClient): """ GIVEN a client WHEN calling DELETE /teams/{id}/members/{id} endpoint @@ -1854,7 +1849,7 @@ def test_search_sources(client: GGClient): @my_vcr.use_cassette("test_list_teams_sources.yaml", ignore_localhost=False) -def test_list_team_sources(client: GGClient, get_team: Callable[[], Team]): +def test_list_team_sources(client: GGClient): """ GIVEN a client WHEN calling GET /sources endpoint @@ -1869,7 +1864,7 @@ def test_list_team_sources(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_search_teams_sources.yaml", ignore_localhost=False) -def test_search_team_sources(client: GGClient, get_team: Callable[[], Team]): +def test_search_team_sources(client: GGClient): """ GIVEN a client WHEN calling GET /sources endpoint @@ -1886,7 +1881,7 @@ def test_search_team_sources(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_delete_team_sources.yaml", ignore_localhost=False) -def test_delete_team_sources(client: GGClient, get_team: Callable[[], Team]): +def test_delete_team_sources(client: GGClient): """ GIVEN a client WHEN calling POST /teams/{id}/sources endpoint @@ -1911,9 +1906,7 @@ def test_delete_team_sources(client: GGClient, get_team: Callable[[], Team]): @my_vcr.use_cassette("test_add_team_sources.yaml", ignore_localhost=False) -def test_add_team_sources( - client: GGClient, get_team: Callable[[], Team], get_source: Callable[[], Source] -): +def test_add_team_sources(client: GGClient): """ GIVEN a client WHEN calling POST /teams/{id}/sources endpoint diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..8fdc1463 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,52 @@ +from pygitguardian.models import Invitation, Source, Team, TeamsParameter +from pygitguardian.models_utils import CursorPaginatedResponse + +from .conftest import create_client + + +def get_source() -> Source: + """ + Return the first source available in the account, + not all accounts have a source so testing from scratch + will require installing a source first + """ + + client = create_client() + paginated_sources = client.list_sources() + assert isinstance( + paginated_sources, CursorPaginatedResponse + ), "Could not fetch sources from GitGuardian" + return paginated_sources.data[0] + + +def get_invitation() -> Invitation: + """ + Return the first invitation available in the account, + there is no invitation by default, one should be + created to setup the test + """ + + client = create_client() + paginated_teams = client.list_invitations() + assert isinstance( + paginated_teams, CursorPaginatedResponse + ), "Could not fetch members from GitGuardian" + + return paginated_teams.data[0] + + +def get_team() -> Team: + """ + Return the first team available in the account, + every account should have at least one team (all incidents) + but we skip it since we cannot add sources to it + """ + + client = create_client() + + paginated_teams = client.list_teams(TeamsParameter(is_global=False)) + assert isinstance( + paginated_teams, CursorPaginatedResponse + ), "Could not fetch teams from GitGuardian" + + return paginated_teams.data[0] From b87bc42c8947438e2cd4c97b8736306c6eba33ca Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Thu, 19 Dec 2024 10:44:06 +0100 Subject: [PATCH 18/20] fix(client): explicit add status code for all response --- pygitguardian/client.py | 30 +++++++++------- pygitguardian/models.py | 34 +++++++++---------- pygitguardian/models_utils.py | 2 ++ tests/cassettes/test_add_team_sources.yaml | 4 +-- .../test_create_team_invitation.yaml | 2 +- tests/cassettes/test_create_team_member.yaml | 2 +- .../test_create_team_member_parameters.yaml | 2 +- tests/cassettes/test_delete_team.yaml | 2 +- .../test_delete_team_invitation.yaml | 2 +- tests/cassettes/test_delete_team_member.yaml | 2 +- tests/cassettes/test_delete_team_sources.yaml | 2 +- tests/cassettes/test_get_team.yaml | 2 +- tests/cassettes/test_global_team.yaml | 2 +- .../cassettes/test_list_team_invitations.yaml | 2 +- tests/cassettes/test_list_team_members.yaml | 2 +- tests/cassettes/test_list_teams_sources.yaml | 2 +- tests/cassettes/test_search_sources.yaml | 2 +- .../test_search_team_invitations.yaml | 2 +- tests/cassettes/test_search_team_members.yaml | 2 +- .../cassettes/test_search_teams_sources.yaml | 4 +-- tests/cassettes/test_update_team.yaml | 2 +- tests/test_client.py | 24 +++++++------ tests/utils.py | 4 +-- 23 files changed, 72 insertions(+), 62 deletions(-) diff --git a/pygitguardian/client.py b/pygitguardian/client.py index daf6ee66..f04b802f 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -27,11 +27,11 @@ from .models import ( APITokensResponse, CreateInvitation, - CreateInvitationParameter, + CreateInvitationParameters, CreateTeam, CreateTeamInvitation, CreateTeamMember, - CreateTeamMemberParameter, + CreateTeamMemberParameters, DeleteMemberParameters, Detail, Document, @@ -40,7 +40,7 @@ HoneytokenResponse, HoneytokenWithContextResponse, Invitation, - InvitationParameter, + InvitationParameters, JWTResponse, JWTService, Member, @@ -56,11 +56,11 @@ SourceParameters, Team, TeamInvitation, - TeamInvitationParameter, + TeamInvitationParameters, TeamMember, - TeamMemberParameter, + TeamMemberParameters, TeamSourceParameters, - TeamsParameter, + TeamsParameters, UpdateMember, UpdateTeam, UpdateTeamSource, @@ -1005,7 +1005,7 @@ def delete_member( def list_teams( self, - parameters: Optional[TeamsParameter] = None, + parameters: Optional[TeamsParameters] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, CursorPaginatedResponse[Team]]: params = parameters.to_dict() if parameters else {} @@ -1021,6 +1021,7 @@ def list_teams( else: obj = load_detail(response) + obj.status_code = response.status_code return obj def get_team( @@ -1100,7 +1101,7 @@ def delete_team( def list_team_invitations( self, team_id: int, - parameters: Optional[TeamInvitationParameter] = None, + parameters: Optional[TeamInvitationParameters] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, CursorPaginatedResponse[TeamInvitation]]: response = self.get( @@ -1117,6 +1118,7 @@ def list_team_invitations( else: obj = load_detail(response) + obj.status_code = response.status_code return obj def create_team_invitation( @@ -1157,7 +1159,7 @@ def delete_team_invitation( def list_team_members( self, team_id: int, - parameters: Optional[TeamMemberParameter] = None, + parameters: Optional[TeamMemberParameters] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, CursorPaginatedResponse[TeamMember]]: response = self.get( @@ -1174,13 +1176,14 @@ def list_team_members( else: obj = load_detail(response) + obj.status_code = response.status_code return obj def create_team_member( self, team_id: int, member: CreateTeamMember, - parameters: Optional[CreateTeamMemberParameter] = None, + parameters: Optional[CreateTeamMemberParameters] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, TeamMember]: response = self.post( @@ -1230,6 +1233,7 @@ def list_sources( else: obj = load_detail(response) + obj.status_code = response.status_code return obj def list_team_sources( @@ -1250,6 +1254,7 @@ def list_team_sources( else: obj = load_detail(response) + obj.status_code = response.status_code return obj def update_team_source( @@ -1272,7 +1277,7 @@ def update_team_source( def list_invitations( self, - parameters: Optional[InvitationParameter] = None, + parameters: Optional[InvitationParameters] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, CursorPaginatedResponse[Invitation]]: response = self.get( @@ -1289,12 +1294,13 @@ def list_invitations( else: obj = load_detail(response) + obj.status_code = response.status_code return obj def create_invitation( self, invitation: CreateInvitation, - parameters: Optional[CreateInvitationParameter] = None, + parameters: Optional[CreateInvitationParameters] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, Invitation]: response = self.post( diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 89cabcbd..0e6579f9 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -1207,15 +1207,15 @@ class DeleteMemberParameters(Base, FromDictMixin): @dataclass -class TeamsParameter(PaginationParameter, SearchParameter, FromDictMixin, ToDictMixin): +class TeamsParameters(PaginationParameter, SearchParameter, FromDictMixin, ToDictMixin): is_global: Optional[bool] = None TeamsParameterSchema = cast( Type[BaseSchema], - marshmallow_dataclass.class_schema(TeamsParameter, base_schema=BaseSchema), + marshmallow_dataclass.class_schema(TeamsParameters, base_schema=BaseSchema), ) -TeamsParameter.SCHEMA = TeamsParameterSchema() +TeamsParameters.SCHEMA = TeamsParameterSchema() @dataclass @@ -1275,7 +1275,7 @@ class IncidentPermission(str, Enum): @dataclass -class TeamInvitationParameter(PaginationParameter, ToDictMixin): +class TeamInvitationParameters(PaginationParameter, ToDictMixin): invitation_id: Optional[int] = None is_team_leader: Optional[bool] = None incident_permission: Optional[IncidentPermission] = None @@ -1292,7 +1292,7 @@ class Meta: exclude_none = True -TeamInvitationParameter.SCHEMA = TeamInvitationParameterSchema() +TeamInvitationParameters.SCHEMA = TeamInvitationParameterSchema() @dataclass @@ -1351,7 +1351,7 @@ class Meta: @dataclass -class TeamMemberParameter(PaginationParameter, SearchParameter, ToDictMixin): +class TeamMemberParameters(PaginationParameter, SearchParameter, ToDictMixin): is_team_leader: Optional[bool] = None incident_permission: Optional[IncidentPermission] = None member_id: Optional[int] = None @@ -1366,13 +1366,13 @@ class TeamMembershipParameterSchema(BaseSchema): @post_load def make_team_member_parameter(self, data: Dict[str, Any], **kwargs: Any): - return TeamMemberParameter(**data) + return TeamMemberParameters(**data) class Meta: exclude_none = True -TeamMemberParameter.SCHEMA = TeamMembershipParameterSchema() +TeamMemberParameters.SCHEMA = TeamMembershipParameterSchema() @dataclass @@ -1402,17 +1402,17 @@ def make_team_member(self, data: Dict[str, Any], **kwargs: Any): @dataclass -class CreateTeamMemberParameter(ToDictMixin): - send_email: bool +class CreateTeamMemberParameters(ToDictMixin): + send_email: Optional[bool] = None CreateTeamMemberParameterSchema = cast( Type[BaseSchema], marshmallow_dataclass.class_schema( - CreateTeamMemberParameter, base_schema=BaseSchema + CreateTeamMemberParameters, base_schema=BaseSchema ), ) -CreateTeamMemberParameter.SCHEMA = CreateTeamMemberParameterSchema() +CreateTeamMemberParameters.SCHEMA = CreateTeamMemberParameterSchema() @dataclass @@ -1483,10 +1483,10 @@ class SourceParameters(TeamSourceParameters): @dataclass -class InvitationParameter( +class InvitationParameters( PaginationParameter, SearchParameter, FromDictMixin, ToDictMixin ): - ordering: Literal["date", "-date"] + ordering: Optional[Literal["date", "-date"]] = None @dataclass @@ -1512,17 +1512,17 @@ def make_invitation(self, data: Dict[str, Any], **kwargs: Any): @dataclass -class CreateInvitationParameter(FromDictMixin, ToDictMixin): +class CreateInvitationParameters(FromDictMixin, ToDictMixin): send_email: Optional[bool] = None CreateInvitationParameterSchema = cast( Type[BaseSchema], marshmallow_dataclass.class_schema( - CreateInvitationParameter, base_schema=BaseSchema + CreateInvitationParameters, base_schema=BaseSchema ), ) -CreateInvitationParameter.SCHEMA = CreateInvitationParameterSchema() +CreateInvitationParameters.SCHEMA = CreateInvitationParameterSchema() @dataclass diff --git a/pygitguardian/models_utils.py b/pygitguardian/models_utils.py index edb75f00..dacae5e1 100644 --- a/pygitguardian/models_utils.py +++ b/pygitguardian/models_utils.py @@ -77,6 +77,7 @@ def __bool__(self) -> bool: return self.status_code == 200 +@dataclass class PaginationParameter(ToDictMixin): """Pagination mixin used for endpoints that support pagination.""" @@ -84,6 +85,7 @@ class PaginationParameter(ToDictMixin): per_page: int = 20 +@dataclass class SearchParameter(ToDictMixin): search: Optional[str] = None diff --git a/tests/cassettes/test_add_team_sources.yaml b/tests/cassettes/test_add_team_sources.yaml index f1d83e75..77d45799 100644 --- a/tests/cassettes/test_add_team_sources.yaml +++ b/tests/cassettes/test_add_team_sources.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: @@ -200,7 +200,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/19/sources?type=azure_devops + uri: https://api.gitguardian.com/v1/teams/19/sources?cursor=&per_page=20&type=azure_devops response: body: string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / diff --git a/tests/cassettes/test_create_team_invitation.yaml b/tests/cassettes/test_create_team_invitation.yaml index 87a8340a..8270d3c1 100644 --- a/tests/cassettes/test_create_team_invitation.yaml +++ b/tests/cassettes/test_create_team_invitation.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_create_team_member.yaml b/tests/cassettes/test_create_team_member.yaml index 0b0f95fd..7b7d16a3 100644 --- a/tests/cassettes/test_create_team_member.yaml +++ b/tests/cassettes/test_create_team_member.yaml @@ -70,7 +70,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_create_team_member_parameters.yaml b/tests/cassettes/test_create_team_member_parameters.yaml index ffde13a7..3a1c7983 100644 --- a/tests/cassettes/test_create_team_member_parameters.yaml +++ b/tests/cassettes/test_create_team_member_parameters.yaml @@ -70,7 +70,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_delete_team.yaml b/tests/cassettes/test_delete_team.yaml index 6e53c4f0..2a393f42 100644 --- a/tests/cassettes/test_delete_team.yaml +++ b/tests/cassettes/test_delete_team.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_delete_team_invitation.yaml b/tests/cassettes/test_delete_team_invitation.yaml index c2e2e35f..e214696c 100644 --- a/tests/cassettes/test_delete_team_invitation.yaml +++ b/tests/cassettes/test_delete_team_invitation.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_delete_team_member.yaml b/tests/cassettes/test_delete_team_member.yaml index 1ffacb98..f6572fc6 100644 --- a/tests/cassettes/test_delete_team_member.yaml +++ b/tests/cassettes/test_delete_team_member.yaml @@ -70,7 +70,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_delete_team_sources.yaml b/tests/cassettes/test_delete_team_sources.yaml index e5ba31a0..ef92f861 100644 --- a/tests/cassettes/test_delete_team_sources.yaml +++ b/tests/cassettes/test_delete_team_sources.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_get_team.yaml b/tests/cassettes/test_get_team.yaml index 29a21ef3..9b400308 100644 --- a/tests/cassettes/test_get_team.yaml +++ b/tests/cassettes/test_get_team.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_global_team.yaml b/tests/cassettes/test_global_team.yaml index 51e8085d..b4b1f221 100644 --- a/tests/cassettes/test_global_team.yaml +++ b/tests/cassettes/test_global_team.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=True + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=True response: body: string: '[{"id":6,"is_global":true,"name":"All incidents","description":null,"gitguardian_url":"http://localhost:3000/workspace/6/settings/user/teams/6"}]' diff --git a/tests/cassettes/test_list_team_invitations.yaml b/tests/cassettes/test_list_team_invitations.yaml index b694a8f4..56598f84 100644 --- a/tests/cassettes/test_list_team_invitations.yaml +++ b/tests/cassettes/test_list_team_invitations.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_list_team_members.yaml b/tests/cassettes/test_list_team_members.yaml index a317d595..409f3eef 100644 --- a/tests/cassettes/test_list_team_members.yaml +++ b/tests/cassettes/test_list_team_members.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_list_teams_sources.yaml b/tests/cassettes/test_list_teams_sources.yaml index 63a20eb3..7010d2ab 100644 --- a/tests/cassettes/test_list_teams_sources.yaml +++ b/tests/cassettes/test_list_teams_sources.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_search_sources.yaml b/tests/cassettes/test_search_sources.yaml index 4de6183c..0307ba2c 100644 --- a/tests/cassettes/test_search_sources.yaml +++ b/tests/cassettes/test_search_sources.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/sources?type=azure_devops + uri: https://api.gitguardian.com/v1/sources?cursor=&per_page=20&type=azure_devops response: body: string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / diff --git a/tests/cassettes/test_search_team_invitations.yaml b/tests/cassettes/test_search_team_invitations.yaml index 9ae0fb86..ef1a29b7 100644 --- a/tests/cassettes/test_search_team_invitations.yaml +++ b/tests/cassettes/test_search_team_invitations.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_search_team_members.yaml b/tests/cassettes/test_search_team_members.yaml index 5897b68a..d1314a89 100644 --- a/tests/cassettes/test_search_team_members.yaml +++ b/tests/cassettes/test_search_team_members.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/cassettes/test_search_teams_sources.yaml b/tests/cassettes/test_search_teams_sources.yaml index 9b972494..cdb952c8 100644 --- a/tests/cassettes/test_search_teams_sources.yaml +++ b/tests/cassettes/test_search_teams_sources.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: @@ -70,7 +70,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams/19/sources?type=azure_devops + uri: https://api.gitguardian.com/v1/teams/19/sources?cursor=&per_page=20&type=azure_devops response: body: string: '[{"id":124,"type":"azure_devops","full_name":"gg-integration-test / diff --git a/tests/cassettes/test_update_team.yaml b/tests/cassettes/test_update_team.yaml index 24bf1b04..f34baba7 100644 --- a/tests/cassettes/test_update_team.yaml +++ b/tests/cassettes/test_update_team.yaml @@ -11,7 +11,7 @@ interactions: User-Agent: - pygitguardian/1.18.0 (Darwin;py3.11.8) method: GET - uri: https://api.gitguardian.com/v1/teams?is_global=False + uri: https://api.gitguardian.com/v1/teams?cursor=&per_page=20&is_global=False response: body: string: diff --git a/tests/test_client.py b/tests/test_client.py index 8600890d..0931be3d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -26,11 +26,11 @@ AccessLevel, APITokensResponse, CreateInvitation, - CreateInvitationParameter, + CreateInvitationParameters, CreateTeam, CreateTeamInvitation, CreateTeamMember, - CreateTeamMemberParameter, + CreateTeamMemberParameters, DeleteMemberParameters, Detail, HoneytokenResponse, @@ -48,11 +48,11 @@ SourceParameters, Team, TeamInvitation, - TeamInvitationParameter, + TeamInvitationParameters, TeamMember, - TeamMemberParameter, + TeamMemberParameters, TeamSourceParameters, - TeamsParameter, + TeamsParameters, UpdateMember, UpdateTeam, UpdateTeamSource, @@ -1585,7 +1585,7 @@ def test_global_team(client: GGClient): THEN the global team is returned """ - result = client.list_teams(parameters=TeamsParameter(is_global=True)) + result = client.list_teams(parameters=TeamsParameters(is_global=True)) assert isinstance(result, CursorPaginatedResponse), result @@ -1657,7 +1657,9 @@ def test_search_team_invitations(client: GGClient): team = get_team() result = client.list_team_invitations( team.id, - parameters=TeamInvitationParameter(incident_permission=IncidentPermission.VIEW), + parameters=TeamInvitationParameters( + incident_permission=IncidentPermission.VIEW + ), ) assert isinstance(result, CursorPaginatedResponse), result @@ -1714,7 +1716,7 @@ def test_search_team_members(client: GGClient): # Every team should have at least one team leader, but an account without a team # will nullify the purpose of this test even though it will pass result = client.list_team_members( - team.id, parameters=TeamMemberParameter(is_team_leader=True) + team.id, parameters=TeamMemberParameters(is_team_leader=True) ) assert isinstance(result, CursorPaginatedResponse), result @@ -1787,7 +1789,7 @@ def test_create_team_member_without_mail(client: GGClient): result = client.create_team_member( team.id, CreateTeamMember(member_to_add.id, False, IncidentPermission.VIEW), - CreateTeamMemberParameter(send_email=False), + CreateTeamMemberParameters(send_email=False), ) assert isinstance(result, TeamMember), result @@ -1808,7 +1810,7 @@ def test_delete_team_member(client: GGClient): team = get_team() team_members = client.list_team_members( - team.id, TeamMemberParameter(is_team_leader=False) + team.id, TeamMemberParameters(is_team_leader=False) ) assert isinstance( team_members, CursorPaginatedResponse @@ -1955,7 +1957,7 @@ def test_send_invitation(client: GGClient): CreateInvitation( email="pygitguardian@example.com", access_level=AccessLevel.MEMBER ), - CreateInvitationParameter(send_email=False), + CreateInvitationParameters(send_email=False), ) assert isinstance(result, Invitation), result diff --git a/tests/utils.py b/tests/utils.py index 8fdc1463..706ed855 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,4 @@ -from pygitguardian.models import Invitation, Source, Team, TeamsParameter +from pygitguardian.models import Invitation, Source, Team, TeamsParameters from pygitguardian.models_utils import CursorPaginatedResponse from .conftest import create_client @@ -44,7 +44,7 @@ def get_team() -> Team: client = create_client() - paginated_teams = client.list_teams(TeamsParameter(is_global=False)) + paginated_teams = client.list_teams(TeamsParameters(is_global=False)) assert isinstance( paginated_teams, CursorPaginatedResponse ), "Could not fetch teams from GitGuardian" From 3efc288da775a0962247cff079e8460c4a656dc4 Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Thu, 19 Dec 2024 14:34:38 +0100 Subject: [PATCH 19/20] fix(models_utils): paginated data now also receives status code when instantiated from 'from_response' --- pygitguardian/models.py | 39 ++++++++++++++++++----------------- pygitguardian/models_utils.py | 9 +++++++- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/pygitguardian/models.py b/pygitguardian/models.py index 0e6579f9..979a6661 100644 --- a/pygitguardian/models.py +++ b/pygitguardian/models.py @@ -28,6 +28,7 @@ Base, BaseSchema, FromDictMixin, + FromDictWithBase, PaginationParameter, SearchParameter, ToDictMixin, @@ -113,7 +114,7 @@ def make_detail_response(self, data: Dict[str, Any], **kwargs: Any) -> "Detail": return Detail(**data) -class Detail(Base, FromDictMixin): +class Detail(FromDictWithBase): """Detail is a response object mostly returned on error or when the api output is a simple string. @@ -146,7 +147,7 @@ def make_match(self, data: Dict[str, Any], **kwargs: Any) -> "Match": return Match(**data) -class Match(Base, FromDictMixin): +class Match(FromDictWithBase): """ Match describes an issue found by GitGuardian. @@ -231,7 +232,7 @@ def make_policy_break(self, data: Dict[str, Any], **kwargs: Any) -> "PolicyBreak return PolicyBreak(**data) -class PolicyBreak(Base, FromDictMixin): +class PolicyBreak(FromDictWithBase): """ PolicyBreak describes a GitGuardian policy break found in a scan. @@ -288,7 +289,7 @@ def make_scan_result(self, data: Dict[str, Any], **kwargs: Any) -> "ScanResult": return ScanResult(**data) -class ScanResult(Base, FromDictMixin): +class ScanResult(FromDictWithBase): """ScanResult is a response object returned on a Content Scan Attributes: @@ -378,7 +379,7 @@ def make_scan_result( return MultiScanResult(**data) -class MultiScanResult(Base, FromDictMixin): +class MultiScanResult(FromDictWithBase): """ScanResult is a response object returned on a Content Scan Attributes: @@ -956,7 +957,7 @@ class Scan(Base, FromDictMixin): @dataclass -class Source(Base, FromDictMixin): +class Source(FromDictWithBase): id: int url: str type: str @@ -1110,7 +1111,7 @@ def make_members_parameters(self, data: Dict[str, Any], **kwargs: Any): @dataclass -class Member(Base, FromDictMixin): +class Member(FromDictWithBase): """ Member represents a user in a GitGuardian account. """ @@ -1168,7 +1169,7 @@ def access_level_value(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, A @dataclass -class UpdateMember(Base, FromDictMixin): +class UpdateMember(FromDictWithBase): """ UpdateMember represents the payload to update a member """ @@ -1182,7 +1183,7 @@ class UpdateMember(Base, FromDictMixin): @dataclass -class UpdateMemberParameters(Base, FromDictMixin): +class UpdateMemberParameters(FromDictWithBase): send_email: Optional[bool] = None @@ -1194,7 +1195,7 @@ class UpdateMemberParameters(Base, FromDictMixin): @dataclass -class DeleteMemberParameters(Base, FromDictMixin): +class DeleteMemberParameters(FromDictWithBase): id: int send_email: Optional[bool] = None @@ -1219,7 +1220,7 @@ class TeamsParameters(PaginationParameter, SearchParameter, FromDictMixin, ToDic @dataclass -class Team(Base, FromDictMixin): +class Team(FromDictWithBase): id: int name: str is_global: bool @@ -1235,7 +1236,7 @@ class Team(Base, FromDictMixin): @dataclass -class CreateTeam(Base, FromDictMixin): +class CreateTeam(FromDictWithBase): name: str description: Optional[str] = "" @@ -1250,7 +1251,7 @@ class CreateTeam(Base, FromDictMixin): @dataclass -class UpdateTeam(Base, FromDictMixin): +class UpdateTeam(FromDictWithBase): id: int name: Optional[str] description: Optional[str] = None @@ -1296,7 +1297,7 @@ class Meta: @dataclass -class TeamInvitation(Base, FromDictMixin): +class TeamInvitation(FromDictWithBase): id: int invitation_id: int team_id: int @@ -1326,7 +1327,7 @@ def make_team_invitation( @dataclass -class CreateTeamInvitation(Base, FromDictMixin): +class CreateTeamInvitation(FromDictWithBase): invitation_id: int is_team_leader: bool incident_permission: IncidentPermission @@ -1376,7 +1377,7 @@ class Meta: @dataclass -class TeamMember(Base, FromDictMixin): +class TeamMember(FromDictWithBase): id: int team_id: int member_id: int @@ -1416,7 +1417,7 @@ class CreateTeamMemberParameters(ToDictMixin): @dataclass -class CreateTeamMember(Base, FromDictMixin): +class CreateTeamMember(FromDictWithBase): member_id: int is_team_leader: bool incident_permission: IncidentPermission @@ -1456,7 +1457,7 @@ class TeamSourceParameters(PaginationParameter, SearchParameter, ToDictMixin): @dataclass -class UpdateTeamSource(Base, FromDictMixin): +class UpdateTeamSource(FromDictWithBase): team_id: int sources_to_add: List[int] sources_to_remove: List[int] @@ -1490,7 +1491,7 @@ class InvitationParameters( @dataclass -class Invitation(Base, FromDictMixin): +class Invitation(FromDictWithBase): id: int email: str access_level: AccessLevel diff --git a/pygitguardian/models_utils.py b/pygitguardian/models_utils.py index dacae5e1..463e7d9d 100644 --- a/pygitguardian/models_utils.py +++ b/pygitguardian/models_utils.py @@ -90,7 +90,11 @@ class SearchParameter(ToDictMixin): search: Optional[str] = None -PaginatedData = TypeVar("PaginatedData", bound=FromDictMixin) +class FromDictWithBase(FromDictMixin, Base): + pass + + +PaginatedData = TypeVar("PaginatedData", bound=FromDictWithBase) @dataclass @@ -107,6 +111,9 @@ def from_response( data = cast( List[PaginatedData], [data_type.from_dict(obj) for obj in response.json()] ) + for element in data: + element.status_code = response.status_code + paginated_response = cls(status_code=response.status_code, data=data) if previous_page := response.links.get("prev"): From 5133106e51bc02314d08cf6eb8be3f5df6e0565b Mon Sep 17 00:00:00 2001 From: Yanne Bensafia Date: Fri, 20 Dec 2024 09:36:39 +0100 Subject: [PATCH 20/20] refactor(client): change query_parameters to parameters for list_members --- pygitguardian/client.py | 4 ++-- pygitguardian/models_utils.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pygitguardian/client.py b/pygitguardian/client.py index f04b802f..1e3fa749 100644 --- a/pygitguardian/client.py +++ b/pygitguardian/client.py @@ -927,13 +927,13 @@ def scan_diff( def list_members( self, - query_parameters: Optional[MembersParameters] = None, + parameters: Optional[MembersParameters] = None, extra_headers: Optional[Dict[str, str]] = None, ) -> Union[Detail, CursorPaginatedResponse[Member]]: response = self.get( endpoint="members", - params=query_parameters.to_dict() if query_parameters else {}, + params=parameters.to_dict() if parameters else {}, extra_headers=extra_headers, ) diff --git a/pygitguardian/models_utils.py b/pygitguardian/models_utils.py index 463e7d9d..91801669 100644 --- a/pygitguardian/models_utils.py +++ b/pygitguardian/models_utils.py @@ -15,6 +15,7 @@ cast, ) +import marshmallow_dataclass from marshmallow import EXCLUDE, Schema from typing_extensions import Self @@ -85,11 +86,25 @@ class PaginationParameter(ToDictMixin): per_page: int = 20 +PaginationParameterSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(PaginationParameter, base_schema=BaseSchema), +) +PaginationParameter.SCHEMA = PaginationParameterSchema() + + @dataclass class SearchParameter(ToDictMixin): search: Optional[str] = None +SearchParameterSchema = cast( + Type[BaseSchema], + marshmallow_dataclass.class_schema(SearchParameter, base_schema=BaseSchema), +) +SearchParameter.SCHEMA = SearchParameterSchema() + + class FromDictWithBase(FromDictMixin, Base): pass