From f74e76d4fcd3ded4802d57c8208e8cefc7289ec8 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Fri, 27 Sep 2024 11:44:33 -0400 Subject: [PATCH 01/10] Add `type` column on `Workspace` model This adds a `type` column on the `Workspace` model in order to identify in the code and API, which type of workspace an object is: - `root`: top-level workspace for a tenant - `default`: the default workspace for a tenant - `standard`: any custom non-root/default workspace Constraints are added to ensure a single root and single default workspace per tenant. --- .../0051_workspace_type_and_more.py | 33 +++++++++++++++++++ rbac/management/workspace/model.py | 19 +++++++++++ tests/management/workspace/test_view.py | 6 ++-- 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 rbac/management/migrations/0051_workspace_type_and_more.py diff --git a/rbac/management/migrations/0051_workspace_type_and_more.py b/rbac/management/migrations/0051_workspace_type_and_more.py new file mode 100644 index 000000000..18f99bfc2 --- /dev/null +++ b/rbac/management/migrations/0051_workspace_type_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.15 on 2024-09-27 14:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("management", "0050_principal_user_id_alter_principal_type"), + ] + + operations = [ + migrations.AddField( + model_name="workspace", + name="type", + field=models.CharField( + choices=[ + ("standard", "Standard"), + ("default", "Default"), + ("root", "Root"), + ], + default="standard", + ), + ), + migrations.AddConstraint( + model_name="workspace", + constraint=models.UniqueConstraint( + condition=models.Q(("type__in", ["root", "default"])), + fields=("tenant_id", "type"), + name="unique_default_root_workspace_per_tenant", + ), + ), + ] diff --git a/rbac/management/workspace/model.py b/rbac/management/workspace/model.py index ba1c9e4ee..dfd068c16 100644 --- a/rbac/management/workspace/model.py +++ b/rbac/management/workspace/model.py @@ -18,6 +18,7 @@ from uuid import uuid4 from django.db import models +from django.db.models import Q, UniqueConstraint from django.utils import timezone from management.rbac_fields import AutoDateTimeField @@ -27,17 +28,35 @@ class Workspace(TenantAwareModel): """A workspace.""" + class Types(models.TextChoices): + STANDARD = "standard" + DEFAULT = "default" + ROOT = "root" + name = models.CharField(max_length=255) uuid = models.UUIDField(default=uuid4, editable=False, unique=True, null=False) parent = models.ForeignKey( "self", to_field="uuid", on_delete=models.PROTECT, related_name="children", null=True, blank=True ) description = models.CharField(max_length=255, null=True, blank=True, editable=True) + type = models.CharField(choices=Types.choices, default=Types.STANDARD, null=False) created = models.DateTimeField(default=timezone.now) modified = AutoDateTimeField(default=timezone.now) class Meta: ordering = ["name", "modified"] + constraints = [ + UniqueConstraint( + fields=["tenant_id", "type"], + name="unique_default_root_workspace_per_tenant", + condition=Q(type__in=["root", "default"]), + ) + ] + + def save(self, *args, **kwargs): + """Override save on model to enforce validations.""" + self.full_clean() + super().save(*args, **kwargs) def ancestors(self): """Return a list of ancestors for a Workspace instance.""" diff --git a/tests/management/workspace/test_view.py b/tests/management/workspace/test_view.py index cabf70323..7820a969b 100644 --- a/tests/management/workspace/test_view.py +++ b/tests/management/workspace/test_view.py @@ -52,7 +52,7 @@ def test_create_workspace(self): "name": "New Workspace", "description": "New Workspace - description", "tenant_id": self.tenant.id, - "parent_id": "cbe9822d-cadb-447d-bc80-8bef773c36ea", + "parent_id": self.init_workspace.uuid, } parent_workspace = Workspace.objects.create(**workspace_data) @@ -182,7 +182,7 @@ def test_partial_update_workspace_with_put_method(self): "name": "New Workspace", "description": "New Workspace - description", "tenant_id": self.tenant.id, - "parent_id": "cbe9822d-cadb-447d-bc80-8bef773c36ea", + "parent_id": self.init_workspace.uuid, } workspace = Workspace.objects.create(**workspace_data) @@ -210,7 +210,7 @@ def test_update_workspace_same_parent(self): "name": "New Workspace", "description": "New Workspace - description", "tenant_id": self.tenant.id, - "parent_id": "cbe9822d-cadb-447d-bc80-8bef773c36ea", + "parent_id": self.init_workspace.uuid, } parent_workspace = Workspace.objects.create(**parent_workspace_data) From 9afc748f6acdef7f1f778bd44db4573e25513f06 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Fri, 27 Sep 2024 14:08:01 -0400 Subject: [PATCH 02/10] Add workspace type tests --- tests/management/workspace/test_model.py | 72 +++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/tests/management/workspace/test_model.py b/tests/management/workspace/test_model.py index dc8178ef0..0cef1c938 100644 --- a/tests/management/workspace/test_model.py +++ b/tests/management/workspace/test_model.py @@ -15,9 +15,11 @@ # along with this program. If not, see . # """Test the workspace model.""" +from api.models import Tenant from management.models import Workspace from tests.identity_request import IdentityRequest +from django.core.exceptions import ValidationError from django.db.models import ProtectedError @@ -47,10 +49,78 @@ def test_delete_fails_when_children(self): self.assertRaises(ProtectedError, parent.delete) def test_ancestors(self): - """Test ancestors on a workspce""" + """Test ancestors on a workspace""" root = Workspace.objects.create(name="Root", tenant=self.tenant, parent=None) level_1 = Workspace.objects.create(name="Level 1", tenant=self.tenant, parent=root) level_2 = Workspace.objects.create(name="Level 2", tenant=self.tenant, parent=level_1) level_3 = Workspace.objects.create(name="Level 3", tenant=self.tenant, parent=level_2) level_4 = Workspace.objects.create(name="Level 4", tenant=self.tenant, parent=level_3) self.assertCountEqual(level_3.ancestors(), [root, level_1, level_2]) + + +class Types(WorkspaceModelTests): + """Test types on a workspace.""" + + def setUp(self): + """Set up the workspace model tests.""" + self.tenant_2 = Tenant.objects.create(tenant_name="foo") + + self.tenant_1_root_workspace = Workspace.objects.create( + name="T1 Root Workspace", tenant=self.tenant, type="root" + ) + self.tenant_1_default_workspace = Workspace.objects.create( + name="T1 Default Workspace", tenant=self.tenant, type="default" + ) + self.tenant_1_standard_workspace = Workspace.objects.create(name="T1 Standard Workspace", tenant=self.tenant) + super().setUp() + + def test_default_value(self): + """Test the default value of a workspace type when not supplied""" + self.assertEqual(self.tenant_1_standard_workspace.type, "standard") + + def test_single_root_per_tenant(self): + """Test tenant can only have one root workspace""" + with self.assertRaises(ValidationError) as assertion: + Workspace.objects.create(name="T1 Root Workspace Number 2", type="root", tenant=self.tenant) + error_messages = assertion.exception.messages + self.assertEqual(len(error_messages), 1) + self.assertIn("unique_default_root_workspace_per_tenant", error_messages[0]) + + def test_single_default_per_tenant(self): + """Test tenant can only have one default workspace""" + with self.assertRaises(ValidationError) as assertion: + Workspace.objects.create(name="T1 Default Workspace Number 2", type="default", tenant=self.tenant) + error_messages = assertion.exception.messages + self.assertEqual(len(error_messages), 1) + self.assertIn("unique_default_root_workspace_per_tenant", error_messages[0]) + + def test_multiple_root_multiple_tenants(self): + """Test that multiple tenants can have more than one root workspace""" + try: + Workspace.objects.create(name="T2 Root Workspace Number 2", type="root", tenant=self.tenant_2) + except ValidationError as e: + self.fail("test_multiple_root_multiple_tenants raised ValidationError unexpectedly") + + def test_multiple_default_multiple_tenants(self): + """Test that multiple tenants can have more than one default workspace""" + try: + Workspace.objects.create(name="T2 Default Workspace Number 2", type="default", tenant=self.tenant_2) + except ValidationError as e: + self.fail("test_multiple_default_multiple_tenants raised ValidationError unexpectedly") + + def test_multiple_standard_per_tenant(self): + """Test tenant can have multiple standard workspaces""" + try: + for n in ["1", "2", "3"]: + Workspace.objects.create(name=f"T1 Standard Workspace Number {n}", type="standard", tenant=self.tenant) + except ValidationError as e: + self.fail("test_multiple_standard_per_tenant raised ValidationError unexpectedly") + + def test_invalid_type(self): + """Test invalid workspace type""" + invalid_type = "foo" + with self.assertRaises(ValidationError) as assertion: + Workspace.objects.create(name="Invalid Type Workspace", type=invalid_type, tenant=self.tenant) + error_messages = assertion.exception.messages + self.assertEqual(len(error_messages), 1) + self.assertEqual(f"Value '{invalid_type}' is not a valid choice.", error_messages[0]) From f8f711ce847e021eed18a1c133d7dce3d24d7545 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Fri, 27 Sep 2024 14:36:49 -0400 Subject: [PATCH 03/10] Add 'type' to Workspace response in spec --- docs/source/specs/typespec/main.tsp | 7 +++++++ docs/source/specs/v2/openapi.v2.yaml | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/docs/source/specs/typespec/main.tsp b/docs/source/specs/typespec/main.tsp index 1d9fa07e4..1466d9a9a 100644 --- a/docs/source/specs/typespec/main.tsp +++ b/docs/source/specs/typespec/main.tsp @@ -96,9 +96,16 @@ namespace Workspaces { description?: string = "Description of Workspace A"; } + enum WorkspaceTypes { + "root", + "default", + "standard" + } + model Workspace { @key uuid: UUID; parent_id?: UUID; + type: WorkspaceTypes; ...BasicWorkspace; ...Timestamps; } diff --git a/docs/source/specs/v2/openapi.v2.yaml b/docs/source/specs/v2/openapi.v2.yaml index e0eefbb06..3dc47af0a 100644 --- a/docs/source/specs/v2/openapi.v2.yaml +++ b/docs/source/specs/v2/openapi.v2.yaml @@ -765,6 +765,7 @@ components: type: object required: - uuid + - type - name - created - modified @@ -773,6 +774,8 @@ components: $ref: '#/components/schemas/UUID' parent_id: $ref: '#/components/schemas/UUID' + type: + $ref: '#/components/schemas/Workspaces.WorkspaceTypes' name: type: string description: Workspace A @@ -821,6 +824,12 @@ components: items: $ref: '#/components/schemas/Workspaces.Workspace' description: List of workspaces + Workspaces.WorkspaceTypes: + type: string + enum: + - root + - default + - standard servers: - url: https://console.redhat.com/{basePath} description: Production Server From 98fe491d9621c53df4474ee148ca0ae14dc349d3 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Fri, 27 Sep 2024 14:37:05 -0400 Subject: [PATCH 04/10] Add 'type' to Workspace serializer as read_only --- rbac/management/workspace/serializer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rbac/management/workspace/serializer.py b/rbac/management/workspace/serializer.py index b012af190..7eed5cc53 100644 --- a/rbac/management/workspace/serializer.py +++ b/rbac/management/workspace/serializer.py @@ -30,6 +30,7 @@ class WorkspaceSerializer(serializers.ModelSerializer): parent_id = serializers.UUIDField(allow_null=True, required=False) created = serializers.DateTimeField(read_only=True) modified = serializers.DateTimeField(read_only=True) + type = serializers.CharField(read_only=True) class Meta: """Metadata for the serializer.""" @@ -42,6 +43,7 @@ class Meta: "description", "created", "modified", + "type", ) def create(self, validated_data): From 951a23b586e091e876386010d4b5fb740b2a2523 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Fri, 27 Sep 2024 14:37:29 -0400 Subject: [PATCH 05/10] Update tests for workspace 'type' support --- tests/management/workspace/test_serializer.py | 3 +++ tests/management/workspace/test_view.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/tests/management/workspace/test_serializer.py b/tests/management/workspace/test_serializer.py index 705e7686a..1f7526364 100644 --- a/tests/management/workspace/test_serializer.py +++ b/tests/management/workspace/test_serializer.py @@ -57,6 +57,7 @@ def test_get_workspace_detail_child(self): "parent_id": str(self.parent.uuid), "created": self._format_timestamps(self.child.created), "modified": self._format_timestamps(self.child.modified), + "type": self.child.type, } self.assertDictEqual(serializer.data, expected_data) @@ -71,6 +72,7 @@ def test_get_workspace_detail_parent(self): "parent_id": None, "created": self._format_timestamps(self.parent.created), "modified": self._format_timestamps(self.parent.modified), + "type": self.parent.type, } self.assertDictEqual(serializer.data, expected_data) @@ -88,6 +90,7 @@ def test_get_workspace_detail_with_ancestry(self): "ancestry": [{"name": self.parent.name, "uuid": str(self.parent.uuid), "parent_id": None}], "created": self._format_timestamps(self.child.created), "modified": self._format_timestamps(self.child.modified), + "type": self.child.type, } self.assertDictEqual(serializer.data, expected_data) diff --git a/tests/management/workspace/test_view.py b/tests/management/workspace/test_view.py index 7820a969b..c33057386 100644 --- a/tests/management/workspace/test_view.py +++ b/tests/management/workspace/test_view.py @@ -69,6 +69,7 @@ def test_create_workspace(self): self.assertNotEquals(data.get("created"), "") self.assertNotEquals(data.get("modified"), "") self.assertEquals(data.get("description"), "Workspace") + self.assertEquals(data.get("type"), "standard") self.assertEqual(response.get("content-type"), "application/json") def test_create_workspace_without_parent(self): @@ -86,6 +87,7 @@ def test_create_workspace_without_parent(self): self.assertNotEquals(data.get("created"), "") self.assertNotEquals(data.get("modified"), "") self.assertEquals(data.get("description"), "Workspace") + self.assertEquals(data.get("type"), "standard") self.assertEqual(response.get("content-type"), "application/json") def test_create_workspace_empty_body(self): @@ -169,6 +171,7 @@ def test_update_workspace(self): self.assertIsNotNone(data.get("uuid")) self.assertNotEquals(data.get("created"), "") self.assertNotEquals(data.get("modified"), "") + self.assertEquals(data.get("type"), "standard") self.assertEquals(data.get("description"), "Updated description") update_workspace = Workspace.objects.filter(id=workspace.id).first() @@ -295,6 +298,7 @@ def test_partial_update_workspace(self): self.assertIsNotNone(data.get("uuid")) self.assertNotEquals(data.get("created"), "") self.assertNotEquals(data.get("modified"), "") + self.assertEquals(data.get("type"), "standard") update_workspace = Workspace.objects.filter(id=workspace.id).first() self.assertEquals(update_workspace.name, "Updated name") @@ -385,6 +389,7 @@ def test_get_workspace(self): self.assertNotEquals(data.get("modified"), "") self.assertEqual(response.get("content-type"), "application/json") self.assertEqual(data.get("ancestry"), None) + self.assertEquals(data.get("type"), "standard") self.assertEqual(response.get("content-type"), "application/json") def test_get_workspace_with_ancestry(self): @@ -405,6 +410,7 @@ def test_get_workspace_with_ancestry(self): data.get("ancestry"), [{"name": self.parent_workspace.name, "uuid": str(self.parent_workspace.uuid), "parent_id": None}], ) + self.assertEquals(data.get("type"), "standard") self.assertEqual(response.get("content-type"), "application/json") self.assertEqual(data.get("ancestry"), None) @@ -426,6 +432,7 @@ def test_get_workspace_with_ancestry(self): data.get("ancestry"), [{"name": self.parent_workspace.name, "uuid": str(self.parent_workspace.uuid), "parent_id": None}], ) + self.assertEquals(data.get("type"), "standard") self.assertEqual(response.get("content-type"), "application/json") def test_get_workspace_not_found(self): From 9f4278d95a1a9cec57b1def26e74bdd89a0489a1 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Thu, 3 Oct 2024 08:56:59 -0400 Subject: [PATCH 06/10] Update migration conflict from main --- ...space_type_and_more.py => 0052_workspace_type_and_more.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename rbac/management/migrations/{0051_workspace_type_and_more.py => 0052_workspace_type_and_more.py} (87%) diff --git a/rbac/management/migrations/0051_workspace_type_and_more.py b/rbac/management/migrations/0052_workspace_type_and_more.py similarity index 87% rename from rbac/management/migrations/0051_workspace_type_and_more.py rename to rbac/management/migrations/0052_workspace_type_and_more.py index 18f99bfc2..551ca2738 100644 --- a/rbac/management/migrations/0051_workspace_type_and_more.py +++ b/rbac/management/migrations/0052_workspace_type_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.15 on 2024-09-27 14:52 +# Generated by Django 4.2.15 on 2024-10-03 12:54 from django.db import migrations, models @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ("management", "0050_principal_user_id_alter_principal_type"), + ("management", "0051_alter_principal_user_id"), ] operations = [ From 281be7e5852f547cb8f83a7a8afec2037ae36b77 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Fri, 27 Sep 2024 16:23:10 -0400 Subject: [PATCH 07/10] Add `type` query param to Workspace spec for list This surfaces an enum in the API spec for `GET` on `/workspaces/` list: - "all" - "standard" - "root" - "default" Where no `?type=` value, or `?type=all` signifies returning all workspace records. --- docs/source/specs/typespec/main.tsp | 8 ++++++++ docs/source/specs/v2/openapi.v2.yaml | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/docs/source/specs/typespec/main.tsp b/docs/source/specs/typespec/main.tsp index 1466d9a9a..9e6c752d7 100644 --- a/docs/source/specs/typespec/main.tsp +++ b/docs/source/specs/typespec/main.tsp @@ -102,6 +102,11 @@ namespace Workspaces { "standard" } + enum WorkspaceTypesQueryParam { + "all", + ...WorkspaceTypes + } + model Workspace { @key uuid: UUID; parent_id?: UUID; @@ -215,6 +220,9 @@ namespace Workspaces { @get op list( @query limit?: int64 = 10; @query offset?: int64 = 0; + + @doc("Defaults to all when param is not supplied.") + @query type?: WorkspaceTypesQueryParam; ): WorkspaceListResponse | Problems.CommonProblems; @doc("Create workspace in tenant") diff --git a/docs/source/specs/v2/openapi.v2.yaml b/docs/source/specs/v2/openapi.v2.yaml index 3dc47af0a..fcf16784f 100644 --- a/docs/source/specs/v2/openapi.v2.yaml +++ b/docs/source/specs/v2/openapi.v2.yaml @@ -27,6 +27,12 @@ paths: type: integer format: int64 default: 0 + - name: type + in: query + required: false + description: Defaults to all workspaces when param is not supplied, or 'all' is supplied as a value. + schema: + $ref: '#/components/schemas/Workspaces.WorkspaceTypesQueryParam' responses: '200': description: The request has succeeded. @@ -830,6 +836,13 @@ components: - root - default - standard + Workspaces.WorkspaceTypesQueryParam: + type: string + enum: + - all + - root + - default + - standard servers: - url: https://console.redhat.com/{basePath} description: Production Server From f9b54e53b1e781e20ad4821fb9602cac015eb684 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Fri, 27 Sep 2024 16:24:44 -0400 Subject: [PATCH 08/10] Add support for `/workspaces/?type=all|standard|default|root` query param When not supplied, or `all` is supplied on the `type` query param, return all records, otherwise validate and subsequently use the `type` param value to filter the queryset accordingly. --- rbac/management/workspace/view.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rbac/management/workspace/view.py b/rbac/management/workspace/view.py index a2c93562c..091c9e485 100644 --- a/rbac/management/workspace/view.py +++ b/rbac/management/workspace/view.py @@ -69,6 +69,20 @@ def retrieve(self, request, *args, **kwargs): """Get a workspace.""" return super().retrieve(request=request, args=args, kwargs=kwargs) + def list(self, request, *args, **kwargs): + """Get a list of workspaces.""" + all_types = "all" + queryset = self.get_queryset() + type_values = Workspace.Types.values + [all_types] + type_field = validate_and_get_key(request.query_params, "type", type_values, all_types) + + if type_field != all_types: + queryset = queryset.filter(type=type_field) + + serializer = self.get_serializer(queryset, many=True) + page = self.paginate_queryset(serializer.data) + return self.get_paginated_response(page) + def destroy(self, request, *args, **kwargs): """Delete a workspace.""" instance = self.get_object() From 4866bd3abcd6e0b63950227782c0e37070f074e8 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Fri, 27 Sep 2024 16:26:06 -0400 Subject: [PATCH 09/10] Add workspace type param tests on the view --- tests/management/workspace/test_view.py | 84 ++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/tests/management/workspace/test_view.py b/tests/management/workspace/test_view.py index 1771aa3e5..59a59c0d9 100644 --- a/tests/management/workspace/test_view.py +++ b/tests/management/workspace/test_view.py @@ -36,7 +36,7 @@ class WorkspaceViewTests(IdentityRequest): """Test the Workspace Model.""" def setUp(self): - """Set up the audit log model tests.""" + """Set up the workspace model tests.""" reload(urls) clear_url_caches() super().setUp() @@ -525,22 +525,88 @@ def test_delete_workspace_unauthorized(self): self.assertEqual(status_code, 403) self.assertEqual(response.get("content-type"), "application/problem+json") - def test_get_workspace_list(self): - """Test for listing workspaces.""" - url = reverse("workspace-list") - client = APIClient() - response = client.get(url, None, format="json", **self.headers) - payload = response.data +@override_settings(V2_APIS_ENABLED=True) +class TestsList(WorkspaceViewTests): + """Tests for listing workspaces.""" + + def setUp(self): + """Set up the workspace model list tests.""" + super().setUp() + self.root_workspace = Workspace.objects.create(name="Root Workspace", tenant=self.tenant, type="root") + self.default_workspace = Workspace.objects.create(name="Default Workspace", tenant=self.tenant, type="default") + + def assertSuccessfulList(self, response, payload): + """Common list success assertions.""" self.assertIsInstance(payload.get("data"), list) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.get("content-type"), "application/json") - self.assertEqual(payload.get("meta").get("count"), Workspace.objects.count()) for keyname in ["meta", "links", "data"]: self.assertIn(keyname, payload) - for keyname in ["name", "uuid", "parent_id", "description"]: + for keyname in ["name", "uuid", "parent_id", "description", "type"]: self.assertIn(keyname, payload.get("data")[0]) + def assertType(self, payload, expected_type): + """Ensure the correct type on data.""" + for ws in payload.get("data"): + self.assertEqual(ws["type"], expected_type) + + def test_workspace_list_unfiltered(self): + """List workspaces unfiltered.""" + url = reverse("workspace-list") + client = APIClient() + response = client.get(url, None, format="json", **self.headers) + payload = response.data + + self.assertSuccessfulList(response, payload) + self.assertEqual(payload.get("meta").get("count"), Workspace.objects.count()) + + def test_workspace_list_all(self): + """List workspaces type=all.""" + url = reverse("workspace-list") + client = APIClient() + response = client.get(f"{url}?type=all", None, format="json", **self.headers) + payload = response.data + + self.assertSuccessfulList(response, payload) + self.assertEqual(payload.get("meta").get("count"), Workspace.objects.count()) + + def test_workspace_list_standard(self): + """List workspaces type=standard.""" + url = reverse("workspace-list") + client = APIClient() + response = client.get(f"{url}?type=standard", None, format="json", **self.headers) + payload = response.data + + self.assertSuccessfulList(response, payload) + self.assertNotEqual(Workspace.objects.count(), Workspace.objects.filter(type="standard").count()) + self.assertEqual(payload.get("meta").get("count"), Workspace.objects.filter(type="standard").count()) + self.assertType(payload, "standard") + + def test_workspace_list_root(self): + """List workspaces type=root.""" + url = reverse("workspace-list") + client = APIClient() + response = client.get(f"{url}?type=root", None, format="json", **self.headers) + payload = response.data + + self.assertSuccessfulList(response, payload) + self.assertEqual(payload.get("meta").get("count"), 1) + self.assertEqual(payload.get("data")[0]["uuid"], str(self.root_workspace.uuid)) + self.assertType(payload, "root") + + def test_workspace_list_default(self): + """List workspaces type=default.""" + url = reverse("workspace-list") + client = APIClient() + response = client.get(f"{url}?type=default", None, format="json", **self.headers) + payload = response.data + + self.assertSuccessfulList(response, payload) + self.assertEqual(payload.get("meta").get("count"), 1) + self.assertEqual(payload.get("data")[0]["uuid"], str(self.default_workspace.uuid)) + self.assertType(payload, "default") + class WorkspaceViewTestsV2Disabled(WorkspaceViewTests): def test_get_workspace_list(self): From b6ca4539daa45bb8be8b1e9fffe51cf6f2decff0 Mon Sep 17 00:00:00 2001 From: Keith Walsh Date: Wed, 2 Oct 2024 08:11:03 -0400 Subject: [PATCH 10/10] Add default value of workspace type enum to spec --- docs/source/specs/typespec/main.tsp | 2 +- docs/source/specs/v2/openapi.v2.yaml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/specs/typespec/main.tsp b/docs/source/specs/typespec/main.tsp index 9e6c752d7..2ca8e8815 100644 --- a/docs/source/specs/typespec/main.tsp +++ b/docs/source/specs/typespec/main.tsp @@ -222,7 +222,7 @@ namespace Workspaces { @query offset?: int64 = 0; @doc("Defaults to all when param is not supplied.") - @query type?: WorkspaceTypesQueryParam; + @query type?: WorkspaceTypesQueryParam = WorkspaceTypesQueryParam.all; ): WorkspaceListResponse | Problems.CommonProblems; @doc("Create workspace in tenant") diff --git a/docs/source/specs/v2/openapi.v2.yaml b/docs/source/specs/v2/openapi.v2.yaml index fcf16784f..fd51f7782 100644 --- a/docs/source/specs/v2/openapi.v2.yaml +++ b/docs/source/specs/v2/openapi.v2.yaml @@ -30,9 +30,10 @@ paths: - name: type in: query required: false - description: Defaults to all workspaces when param is not supplied, or 'all' is supplied as a value. + description: Defaults to all when param is not supplied. schema: $ref: '#/components/schemas/Workspaces.WorkspaceTypesQueryParam' + default: all responses: '200': description: The request has succeeded.