From 5af0c3cb875ff0f9ba4ed92a64d199e2b766b0c7 Mon Sep 17 00:00:00 2001 From: TaeHyoungKwon Date: Thu, 12 Aug 2021 21:57:59 +0900 Subject: [PATCH 1/6] TH-103 feat: add retrieve_page_list api --- v2/mock_api/src/config/api.py | 39 ++++++++++++++++++++++++++++++++ v2/mock_api/src/config/schema.py | 8 +++++++ 2 files changed, 47 insertions(+) diff --git a/v2/mock_api/src/config/api.py b/v2/mock_api/src/config/api.py index 317439b..e87a50a 100644 --- a/v2/mock_api/src/config/api.py +++ b/v2/mock_api/src/config/api.py @@ -25,6 +25,7 @@ NoteCreateResponse, NoteListResponse, NoteRequestBody, + PageListOut, SendEmailAuthIn, SendEmailAuthOut, SignUpRequestIn, @@ -287,3 +288,41 @@ def retrieve_topic_list(request, note_id): }, {"id": 3, "title": "topic3 title", "created_at": datetime.now(), "is_approved": True, "owner_id": 123}, ] + + +@api.get( + "/topics/{topic_id}/pages", + summary="page 리스트 보기", + response={200: List[PageListOut]}, +) +def retrieve_page_list(request, topic_id): + return 200, [ + { + "id": 1, + "title": "page1 title", + "content": "page1 content", + "created_at": datetime.now(), + "owners": [1, 2, 3], + }, + { + "id": 2, + "title": "page2 title", + "content": "page2 content", + "created_at": datetime.now(), + "owners": [1, 2, 3], + }, + { + "id": 3, + "title": "page3 title", + "content": "page3 content", + "created_at": datetime.now(), + "owners": [1, 2, 3], + }, + { + "id": 4, + "title": "page4 title", + "content": "page4 content", + "created_at": datetime.now(), + "owners": [1, 2, 3], + }, + ] diff --git a/v2/mock_api/src/config/schema.py b/v2/mock_api/src/config/schema.py index 8731b11..d126fe2 100644 --- a/v2/mock_api/src/config/schema.py +++ b/v2/mock_api/src/config/schema.py @@ -303,3 +303,11 @@ class TopicListOut(Schema): created_at: datetime is_approved: bool owner_id: int + + +class PageListOut(Schema): + id: int + title: str + content: str + created_at: datetime + owners: List[int] From 2636663b96a1c2bfd7a22d8ff19511cdc7424292 Mon Sep 17 00:00:00 2001 From: TaeHyoung Kwon Date: Sun, 15 Aug 2021 17:33:40 +0900 Subject: [PATCH 2/6] Update prlint.json --- .github/prlint.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/prlint.json b/.github/prlint.json index ca34afa..3eb6743 100644 --- a/.github/prlint.json +++ b/.github/prlint.json @@ -1,6 +1,8 @@ -"title": [ - { - "pattern": "^(TH-|JH-|SH-|JG-)\d+: \w+$", - "message": "{personal initial above}-{issue number}: {pr title} - ex) TH-150: add test prlint.json" - } -] +{ + "title":[ + { + "pattern": "^(TH-|JH-|SH-|JG-)\d+: \w+$", + "message": "{personal initial above}-{issue number}: {pr title} - ex) TH-150: add test prlint.json" + } + ] +} From eea8b7b9b168e419d86b270b3cf296a148ab5262 Mon Sep 17 00:00:00 2001 From: wlsrn3684 <50126441+wlsrn3684@users.noreply.github.com> Date: Tue, 17 Aug 2021 01:40:39 +0900 Subject: [PATCH 3/6] JG-105 Add issue List API --- v2/src/config/urls.py | 1 + v2/src/ctrlfbe/issue_urls.py | 9 +++++ v2/src/ctrlfbe/serializers.py | 14 +++++++- v2/src/ctrlfbe/swagger.py | 8 +++++ v2/src/ctrlfbe/test.py | 67 ++++++++++++++++++++++++++++++++++- v2/src/ctrlfbe/views.py | 21 +++++++++-- 6 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 v2/src/ctrlfbe/issue_urls.py diff --git a/v2/src/config/urls.py b/v2/src/config/urls.py index 3a12c56..782a7d0 100644 --- a/v2/src/config/urls.py +++ b/v2/src/config/urls.py @@ -25,4 +25,5 @@ path("api/auth/", include("ctrlf_auth.urls"), name="auth"), path("api/notes/", include("ctrlfbe.note_urls"), name="notes"), path("api/topics/", include("ctrlfbe.topic_urls"), name="topics"), + path("api/issues/", include("ctrlfbe.issue_urls"), name="issues"), ] diff --git a/v2/src/ctrlfbe/issue_urls.py b/v2/src/ctrlfbe/issue_urls.py new file mode 100644 index 0000000..47f28d1 --- /dev/null +++ b/v2/src/ctrlfbe/issue_urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from .views import IssueListView + +app_name = "issues" + +urlpatterns = [ + path("", IssueListView.as_view(), name="issue_list"), +] diff --git a/v2/src/ctrlfbe/serializers.py b/v2/src/ctrlfbe/serializers.py index 09952c3..0e22c1e 100644 --- a/v2/src/ctrlfbe/serializers.py +++ b/v2/src/ctrlfbe/serializers.py @@ -1,6 +1,18 @@ from rest_framework import serializers -from .models import Note, Page, Topic +from .models import ContentRequest, Issue, Note, Page, Topic + + +class ContentRequestSerializer(serializers.ModelSerializer): + class Meta: + model = ContentRequest + fields = "__all__" + + +class IssueSerializer(serializers.ModelSerializer): + class Meta: + model = Issue + fields = "__all__" class NoteListSerializer(serializers.ListSerializer): diff --git a/v2/src/ctrlfbe/swagger.py b/v2/src/ctrlfbe/swagger.py index f5bd56a..4feaeef 100644 --- a/v2/src/ctrlfbe/swagger.py +++ b/v2/src/ctrlfbe/swagger.py @@ -1,4 +1,5 @@ from ctrlfbe.serializers import ( + IssueSerializer, NoteListQuerySerializer, NoteSerializer, PageSerializer, @@ -32,3 +33,10 @@ "operation_description": "Cursor based pagination 처리된 Note List를 리턴 합니다", "tags": ["메인 화면"], } + +SWAGGER_ISSUE_LIST_VIEW = { + "responses": {200: IssueSerializer(many=True)}, + "operation_summary": "Issue List API", + "operation_description": "모든 issue들의 list를 리턴해줍니다", + "tags": ["이슈 화면"], +} diff --git a/v2/src/ctrlfbe/test.py b/v2/src/ctrlfbe/test.py index 0200b5d..f3a6543 100644 --- a/v2/src/ctrlfbe/test.py +++ b/v2/src/ctrlfbe/test.py @@ -1,9 +1,74 @@ from ctrlf_auth.models import CtrlfUser -from ctrlfbe.models import Note, Page, Topic +from ctrlfbe.models import ( + CtrlfActionType, + CtrlfContentType, + CtrlfIssueStatus, + Issue, + Note, + Page, + Topic, +) from django.test import Client, TestCase from django.urls import reverse from rest_framework import status +from .serializers import ContentRequestSerializer + + +class TestIssueList(TestCase): + def setUp(self): + self.c = Client() + self.owner = CtrlfUser.objects.create_user(email="creator@test.com", password="12345") + self.note = Note.objects.create(title="test note") + self.note.owners.add(self.owner) + self.creator = CtrlfUser.objects.create_user(email="owner@test.com", password="12345") + + def _add_issues(self): + issue_list = [] + for i in range(10): + content_request_data = { + "user": self.creator, + "sub_id": self.note.id, + "type": CtrlfContentType.NOTE, + "action": CtrlfActionType.CREATE, + "reason": "test reason{}".format(i + 1), + "is_active": True, + } + serializer = ContentRequestSerializer(content_request_data) + issue_data = { + "owner": self.owner, + "title": "test issue{}".format(i + 1), + "content": "test content{}".format(i + 1), + "status": CtrlfIssueStatus.REQUESTED, + "content_request": serializer.data, + } + issue = Issue.objects.create(**issue_data) + issue_list.append(issue) + return issue_list + + def _call_api(self): + return self.c.get(reverse("issues:issue_list")) + + def test_issue_list_should_return_200(self): + # Given: 이미 저장된 issue들 + issue_list = self._add_issues() + # When : API 실행 + response = self._call_api() + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : 이미 저장된 issue 개수와 같아야 함. + response = response.data + self.assertEqual(len(response), len(issue_list)) + + def test_issue_list_should_return_200_by_empty_issue_list(self): + # Given: 저장되지 않은 issue들 + # When : API 실행 + response = self._call_api() + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : 빈 배열을 return 해야함. + self.assertEqual(response.data, []) + class TestNoteDetail(TestCase): def setUp(self): diff --git a/v2/src/ctrlfbe/views.py b/v2/src/ctrlfbe/views.py index 237404d..2a10d72 100644 --- a/v2/src/ctrlfbe/views.py +++ b/v2/src/ctrlfbe/views.py @@ -1,6 +1,7 @@ from typing import List, Optional from ctrlfbe.swagger import ( + SWAGGER_ISSUE_LIST_VIEW, SWAGGER_NOTE_DETAIL_VIEW, SWAGGER_NOTE_LIST_VIEW, SWAGGER_PAGE_LIST_VIEW, @@ -13,8 +14,13 @@ from rest_framework.views import APIView from .constants import ERR_NOT_FOUND_MSG_MAP, ERR_UNEXPECTED, MAX_PRINTABLE_NOTE_COUNT -from .models import Note, Page, Topic -from .serializers import NoteSerializer, PageSerializer, TopicSerializer +from .models import Issue, Note, Page, Topic +from .serializers import ( + IssueSerializer, + NoteSerializer, + PageSerializer, + TopicSerializer, +) class BaseContentView(APIView): @@ -87,3 +93,14 @@ class PageListView(BaseContentView): @swagger_auto_schema(**SWAGGER_PAGE_LIST_VIEW) def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) + + +class IssueListView(BaseContentView): + authentication_classes: List[str] = [] + + @swagger_auto_schema(**SWAGGER_ISSUE_LIST_VIEW) + def get(self, request): + issues = Issue.objects.all() + + serializer = IssueSerializer(issues, many=True) + return Response(serializer.data, status.HTTP_200_OK) From 8753e4e6828ce063be93f7a685589c6fc07c317e Mon Sep 17 00:00:00 2001 From: jingu Date: Wed, 1 Sep 2021 03:52:09 +0900 Subject: [PATCH 4/6] fix merge error --- v2/src/ctrlfbe/swagger.py | 5 ++--- v2/src/ctrlfbe/views.py | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/v2/src/ctrlfbe/swagger.py b/v2/src/ctrlfbe/swagger.py index 77eaf52..6d1d0b5 100644 --- a/v2/src/ctrlfbe/swagger.py +++ b/v2/src/ctrlfbe/swagger.py @@ -35,13 +35,13 @@ "tags": ["메인 화면"], } -<<<<<<< HEAD SWAGGER_ISSUE_LIST_VIEW = { "responses": {200: IssueSerializer(many=True)}, "operation_summary": "Issue List API", "operation_description": "모든 issue들의 list를 리턴해줍니다", "tags": ["이슈 화면"], -======= +} + SWAGGER_NOTE_CREATE_VIEW = { "operation_summary": "Note Create API", "operation_description": "비활성화된 Note와 이슈를 생성 합니다.", @@ -61,5 +61,4 @@ "operation_summary": "Page Detail API", "operation_description": "page_id에 해당하는 Page의 상세 내용을 리턴합니다", "tags": ["디테일 화면"], ->>>>>>> 8afbcec26e912e880b5ca0aa71731f7c50dfc9cf } diff --git a/v2/src/ctrlfbe/views.py b/v2/src/ctrlfbe/views.py index d8e037b..c7a94d4 100644 --- a/v2/src/ctrlfbe/views.py +++ b/v2/src/ctrlfbe/views.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional from ctrlfbe.mixins import CtrlfAuthenticationMixin from ctrlfbe.swagger import ( @@ -18,15 +18,13 @@ from rest_framework.views import APIView from .constants import ERR_NOT_FOUND_MSG_MAP, ERR_UNEXPECTED, MAX_PRINTABLE_NOTE_COUNT -from .models import Issue, Note, Page, Topic -from .serializers import ( - IssueSerializer, -from .models import CtrlfIssueStatus, Note, Page, Topic +from .models import CtrlfIssueStatus, Issue, Note, Page, Topic from .serializers import ( IssueCreateSerializer, NoteSerializer, PageSerializer, TopicSerializer, + IssueSerializer, ) @@ -141,6 +139,8 @@ def get(self, request): serializer = IssueSerializer(issues, many=True) return Response(serializer.data, status.HTTP_200_OK) + + class PageDetailUpdateDeleteView(BaseContentView): parent_model = Page serializer = PageSerializer From 1a01161da79011f2d4a6f569bb3982735218866f Mon Sep 17 00:00:00 2001 From: jingu Date: Wed, 1 Sep 2021 18:58:37 +0900 Subject: [PATCH 5/6] finish issue list api --- v2/src/ctrlfbe/constants.py | 1 + .../migrations/0006_auto_20210901_1507.py | 30 +++ v2/src/ctrlfbe/models.py | 11 +- v2/src/ctrlfbe/serializers.py | 65 ++++- v2/src/ctrlfbe/views.py | 49 +++- v2/src/tests/test_content_list_detail.py | 67 +----- v2/src/tests/test_issue.py | 225 ++++++++++++++++++ 7 files changed, 370 insertions(+), 78 deletions(-) create mode 100644 v2/src/ctrlfbe/migrations/0006_auto_20210901_1507.py create mode 100644 v2/src/tests/test_issue.py diff --git a/v2/src/ctrlfbe/constants.py b/v2/src/ctrlfbe/constants.py index f14fd14..36e42f9 100644 --- a/v2/src/ctrlfbe/constants.py +++ b/v2/src/ctrlfbe/constants.py @@ -1,4 +1,5 @@ MAX_PRINTABLE_NOTE_COUNT = 30 +MAX_PRINTABLE_ISSUE_COUNT = 30 ERR_NOTE_NOT_FOUND = "노트를 찾을 수 없습니다." ERR_TOPIC_NOT_FOUND = "토픽을 찾을 수 없습니다." ERR_PAGE_NOT_FOUND = "페이지를 찾을 수 없습니다." diff --git a/v2/src/ctrlfbe/migrations/0006_auto_20210901_1507.py b/v2/src/ctrlfbe/migrations/0006_auto_20210901_1507.py new file mode 100644 index 0000000..9bda3f5 --- /dev/null +++ b/v2/src/ctrlfbe/migrations/0006_auto_20210901_1507.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.5 on 2021-09-01 06:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ctrlfbe", "0005_page_is_approved"), + ] + + operations = [ + migrations.AddField( + model_name="contentrequest", + name="status", + field=models.CharField( + choices=[("PENDING", "보류"), ("REJECTED", "거절"), ("ACCEPTED", "승인")], + default="PENDING", + help_text="Content 상태들", + max_length=30, + ), + ), + migrations.AlterField( + model_name="issue", + name="status", + field=models.CharField( + choices=[("REQUESTED", "요청"), ("CLOSED", "닫힘")], help_text="Issue 상태들", max_length=30 + ), + ), + ] diff --git a/v2/src/ctrlfbe/models.py b/v2/src/ctrlfbe/models.py index 9c60933..cf6d307 100644 --- a/v2/src/ctrlfbe/models.py +++ b/v2/src/ctrlfbe/models.py @@ -17,16 +17,23 @@ class CtrlfActionType(models.TextChoices): class CtrlfIssueStatus(models.TextChoices): REQUESTED = "REQUESTED", "요청" - REJECTED = "REJECTED", "거절" - APPROVED = "APPROVED", "승인" CLOSED = "CLOSED", "닫힘" +class CtrlfContentStatus(models.TextChoices): + PENDING = "PENDING", "보류" + REJECTED = "REJECTED", "거절" + ACCEPTED = "ACCEPTED", "승인" + + class ContentRequest(CommonTimestamp): user = models.ForeignKey(CtrlfUser, on_delete=models.CASCADE, help_text="수정 혹은 삭제의 주체자") sub_id = models.IntegerField(help_text="type에 대한 id") type = models.CharField(max_length=30, choices=CtrlfContentType.choices, help_text="NOTE, TOPIC, PAGE") action = models.CharField(max_length=30, choices=CtrlfActionType.choices, help_text="CRUD") + status = models.CharField( + max_length=30, choices=CtrlfContentStatus.choices, default=CtrlfContentStatus.PENDING, help_text="Content 상태들" + ) reason = models.TextField(default="", help_text="수정 혹은 삭제 이유") is_active = models.BooleanField(default=False) diff --git a/v2/src/ctrlfbe/serializers.py b/v2/src/ctrlfbe/serializers.py index 9415809..3fc2b44 100644 --- a/v2/src/ctrlfbe/serializers.py +++ b/v2/src/ctrlfbe/serializers.py @@ -1,10 +1,9 @@ from rest_framework import serializers -from .models import ContentRequest, Issue, Note, Page, Topic - from .models import ( ContentRequest, CtrlfActionType, + CtrlfContentStatus, CtrlfContentType, Issue, Note, @@ -16,13 +15,70 @@ class ContentRequestSerializer(serializers.ModelSerializer): class Meta: model = ContentRequest - fields = "__all__" + fields = ["user", "sub_id", "type", "action", "reason", "status"] class IssueSerializer(serializers.ModelSerializer): + content_request = ContentRequestSerializer() + class Meta: model = Issue - fields = "__all__" + fields = ["id", "owner", "title", "content", "status", "content_request"] + + def to_representation(self, instance): + def get_note_owners(note_id, creator): + note = Note.objects.get(id=note_id) + serializer = NoteSerializer(note) + serializered_note = serializer.data + + creator["note"] = serializered_note["owners"] + return creator + + def get_topic_owners(topic_id, creator): + topic = Topic.objects.get(id=topic_id) + serializer = TopicSerializer(topic) + serializered_topic = serializer.data + creator["topic"] = serializered_topic["owners"] + return get_note_owners(serializered_topic["note"], creator) + + def get_page_owners(page_id, creator): + page = Page.objects.get(id=page_id) + serializer = PageSerializer(page) + serializered_page = serializer.data + creator["page"] = serializered_page["owners"] + return get_topic_owners(serializered_page["topic"], creator) + + def make_creator(type, target_id): + creator = { + "note": None, + "topic": None, + "page": None, + } + + if type == "NOTE": + creator = get_note_owners(target_id, creator) + elif type == "TOPIC": + creator = get_topic_owners(target_id, creator) + elif type == "PAGE": + creator = get_page_owners(target_id, creator) + + return creator + + issue = super().to_representation(instance) + creator = make_creator(issue["content_request"]["type"], issue["content_request"]["sub_id"]) + + return { + "id": issue["id"], + "title": issue["title"], + "content": issue["content"], + "action": issue["content_request"]["action"], + "content_id": issue["content_request"]["sub_id"], + "content_type": issue["content_request"]["type"], + "content_status": issue["content_request"]["status"], + "issue_status": issue["status"], + "reason": issue["content_request"]["reason"], + "creator": creator, + } class NoteListSerializer(serializers.ListSerializer): @@ -56,6 +112,7 @@ def create(self, validated_data): "sub_id": note.id, "type": CtrlfContentType.NOTE, "action": CtrlfActionType.CREATE, + "status": CtrlfContentStatus.PENDING, "reason": "create note", } content_request = ContentRequest.objects.create(**content_request_data) diff --git a/v2/src/ctrlfbe/views.py b/v2/src/ctrlfbe/views.py index c7a94d4..f5f9746 100644 --- a/v2/src/ctrlfbe/views.py +++ b/v2/src/ctrlfbe/views.py @@ -1,3 +1,4 @@ +import json from typing import List, Optional from ctrlfbe.mixins import CtrlfAuthenticationMixin @@ -17,14 +18,19 @@ from rest_framework.response import Response from rest_framework.views import APIView -from .constants import ERR_NOT_FOUND_MSG_MAP, ERR_UNEXPECTED, MAX_PRINTABLE_NOTE_COUNT +from .constants import ( + ERR_NOT_FOUND_MSG_MAP, + ERR_UNEXPECTED, + MAX_PRINTABLE_ISSUE_COUNT, + MAX_PRINTABLE_NOTE_COUNT, +) from .models import CtrlfIssueStatus, Issue, Note, Page, Topic from .serializers import ( IssueCreateSerializer, + IssueSerializer, NoteSerializer, PageSerializer, TopicSerializer, - IssueSerializer, ) @@ -130,15 +136,46 @@ def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) -class IssueListView(BaseContentView): +class IssueListView(CtrlfAuthenticationMixin, APIView): authentication_classes: List[str] = [] @swagger_auto_schema(**SWAGGER_ISSUE_LIST_VIEW) def get(self, request): - issues = Issue.objects.all() + ctrlf_user = self._ctrlf_authentication(request) - serializer = IssueSerializer(issues, many=True) - return Response(serializer.data, status.HTTP_200_OK) + current_cursor = int(request.query_params["cursor"]) + typeList = json.loads(request.query_params["type"]) + mine = request.query_params["mine"] + + issues = Issue.objects.all() + filtered_issues = issues + + if mine == "true": + temp_list = [] + for issue in filtered_issues: + if ctrlf_user.id == issue["owner_id"]: + temp_list.append(issue) + filtered_issues = temp_list + + if len(typeList) > 0: + temp_list = [] + for issue in filtered_issues: + if typeList in issue["content_type"]: + temp_list.append(issue) + filtered_issues = temp_list + + if current_cursor >= len(issues): + return Response({"next_cursor": len(issues), "issues": []}, status.HTTP_200_OK) + elif current_cursor + MAX_PRINTABLE_ISSUE_COUNT > len(issues): + sliced_issues = issues[current_cursor : len(issues)] + next_cursor = len(issues) + else: + sliced_issues = issues[current_cursor : current_cursor + MAX_PRINTABLE_ISSUE_COUNT] + next_cursor = current_cursor + MAX_PRINTABLE_ISSUE_COUNT + serializer = IssueSerializer(data=sliced_issues, many=True) + serializer.is_valid() + serialized_issues = serializer.data + return Response(data={"next_cursor": next_cursor, "issues": serialized_issues}, status=status.HTTP_200_OK) class PageDetailUpdateDeleteView(BaseContentView): diff --git a/v2/src/tests/test_content_list_detail.py b/v2/src/tests/test_content_list_detail.py index 2125dc6..d1a393c 100644 --- a/v2/src/tests/test_content_list_detail.py +++ b/v2/src/tests/test_content_list_detail.py @@ -1,74 +1,9 @@ from ctrlf_auth.models import CtrlfUser -from ctrlfbe.models import ( - CtrlfActionType, - CtrlfContentType, - CtrlfIssueStatus, - Issue, - Note, - Page, - Topic, -) +from ctrlfbe.models import Note, Page, Topic from django.test import Client, TestCase from django.urls import reverse from rest_framework import status -from .serializers import ContentRequestSerializer - - -class TestIssueList(TestCase): - def setUp(self): - self.c = Client() - self.owner = CtrlfUser.objects.create_user(email="creator@test.com", password="12345") - self.note = Note.objects.create(title="test note") - self.note.owners.add(self.owner) - self.creator = CtrlfUser.objects.create_user(email="owner@test.com", password="12345") - - def _add_issues(self): - issue_list = [] - for i in range(10): - content_request_data = { - "user": self.creator, - "sub_id": self.note.id, - "type": CtrlfContentType.NOTE, - "action": CtrlfActionType.CREATE, - "reason": "test reason{}".format(i + 1), - "is_active": True, - } - serializer = ContentRequestSerializer(content_request_data) - issue_data = { - "owner": self.owner, - "title": "test issue{}".format(i + 1), - "content": "test content{}".format(i + 1), - "status": CtrlfIssueStatus.REQUESTED, - "content_request": serializer.data, - } - issue = Issue.objects.create(**issue_data) - issue_list.append(issue) - return issue_list - - def _call_api(self): - return self.c.get(reverse("issues:issue_list")) - - def test_issue_list_should_return_200(self): - # Given: 이미 저장된 issue들 - issue_list = self._add_issues() - # When : API 실행 - response = self._call_api() - # Then : 상태코드 200 - self.assertEqual(response.status_code, status.HTTP_200_OK) - # And : 이미 저장된 issue 개수와 같아야 함. - response = response.data - self.assertEqual(len(response), len(issue_list)) - - def test_issue_list_should_return_200_by_empty_issue_list(self): - # Given: 저장되지 않은 issue들 - # When : API 실행 - response = self._call_api() - # Then : 상태코드 200 - self.assertEqual(response.status_code, status.HTTP_200_OK) - # And : 빈 배열을 return 해야함. - self.assertEqual(response.data, []) - class TestNoteDetail(TestCase): def setUp(self): diff --git a/v2/src/tests/test_issue.py b/v2/src/tests/test_issue.py new file mode 100644 index 0000000..541b321 --- /dev/null +++ b/v2/src/tests/test_issue.py @@ -0,0 +1,225 @@ +from ctrlf_auth.models import CtrlfUser +from ctrlfbe.models import ( + ContentRequest, + CtrlfActionType, + CtrlfContentStatus, + CtrlfContentType, + CtrlfIssueStatus, + Issue, + Note, + Page, + Topic, +) +from django.test import Client, TestCase +from django.urls import reverse +from rest_framework import status + + +class TestIssueList(TestCase): + def setUp(self): + self.c = Client() + self.note_creator1 = CtrlfUser.objects.create_user(email="note_creator1@test.com", password="12345") + self.note_creator2 = CtrlfUser.objects.create_user(email="note_creator2@test.com", password="12345") + self.topic_creator = CtrlfUser.objects.create_user(email="topic_creator@test.com", password="12345") + self.page_creator1 = CtrlfUser.objects.create_user(email="page_creator1@test.com", password="12345") + self.page_creator2 = CtrlfUser.objects.create_user(email="page_creator2@test.com", password="12345") + self.note = Note.objects.create(title="test note") + self.note.owners.add(self.note_creator1) + self.note.owners.add(self.note_creator2) + topic_data = {"note": self.note, "title": "test topic"} + self.topic = Topic.objects.create(**topic_data) + self.topic.owners.add(self.topic_creator) + page_data = {"topic": self.topic, "title": "test page"} + self.page = Page.objects.create(**page_data) + self.page.owners.add(self.page_creator1) + self.page.owners.add(self.page_creator2) + self.owner = CtrlfUser.objects.create_user(email="owner@test.com", password="12345") + + def _add_issues(self, count): + issue_list = [] + for i in range(0, count): + content_request_data = { + "user": self.note_creator1, + "sub_id": self.note.id, + "type": CtrlfContentType.NOTE, + "action": CtrlfActionType.CREATE, + "status": CtrlfContentStatus.PENDING, + "reason": "test reason{}".format(i + 1), + "is_active": True, + } + content_request = ContentRequest.objects.create(**content_request_data) + issue_data = { + "owner": self.owner, + "title": "test issue{}".format(i + 1), + "content": "test content{}".format(i + 1), + "status": CtrlfIssueStatus.REQUESTED, + "content_request": content_request, + } + issue = Issue.objects.create(**issue_data) + issue_list.append(issue) + return issue_list + + def _add_topic_issue(self): + content_request_data = { + "user": self.note_creator1, + "sub_id": self.topic.id, + "type": CtrlfContentType.TOPIC, + "action": CtrlfActionType.CREATE, + "status": CtrlfContentStatus.PENDING, + "reason": "test reason", + "is_active": True, + } + content_request = ContentRequest.objects.create(**content_request_data) + issue_data = { + "owner": self.owner, + "title": "test issue", + "content": "test content", + "status": CtrlfIssueStatus.REQUESTED, + "content_request": content_request, + } + issue = Issue.objects.create(**issue_data) + return issue + + def _add_page_issue(self): + content_request_data = { + "user": self.note_creator1, + "sub_id": self.page.id, + "type": CtrlfContentType.PAGE, + "action": CtrlfActionType.CREATE, + "status": CtrlfContentStatus.PENDING, + "reason": "test reason", + "is_active": True, + } + content_request = ContentRequest.objects.create(**content_request_data) + issue_data = { + "owner": self.owner, + "title": "test issue", + "content": "test content", + "status": CtrlfIssueStatus.REQUESTED, + "content_request": content_request, + } + issue = Issue.objects.create(**issue_data) + return issue + + def _call_api(self, cursor, type, mine): + return self.c.get(reverse("issues:issue_list"), {"cursor": cursor, "type": type, "mine": mine}) + + def test_issue_list_should_return_200(self): + # Given: 2개의 이슈를 생성하고, cursor는 0, type은 [], mine은 false로 주어진다. + self._add_issues(2) + given_cursor = 0 + given_type = "[]" + given_mine = "false" + # When : API 실행 + response = self._call_api(given_cursor, given_type, given_mine) + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : 첫번째 이슈의 title은 "test issue1" 이어야 한다. + self.assertEqual(response.data["issues"][0]["title"], "test issue1") + # And : 두번째 이슈의 title은 "test issue2" 이어야 한다. + self.assertEqual(response.data["issues"][1]["title"], "test issue2") + + def test_issue_list_shoud_return_200_and_note_owner_is_list(self): + # Given: 1개의 이슈를 생성하고, cursor는 0, type은 [], mine은 false로 주어진다. + self._add_issues(1) + given_cursor = 0 + given_type = "[]" + given_mine = "false" + # When : API 실행 + response = self._call_api(given_cursor, given_type, given_mine) + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : 이슈의 creator의 노트의 값은 [1, 2] 이어야 한다. + self.assertEqual(response.data["issues"][0]["creator"]["note"], [1, 2]) + + def test_issue_list_shoud_return_200_and_topic_owner_is_list(self): + # Given: 1개의 콘텐츠 타입이 토픽인 이슈를 생성하고, cursor는 0, type은 [], mine은 false로 주어진다. + self._add_topic_issue() + given_cursor = 0 + given_type = "[]" + given_mine = "false" + # When : API 실행 + response = self._call_api(given_cursor, given_type, given_mine) + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : 이슈의 creator의 노트의 값은 [1, 2] 이어야 한다. + self.assertEqual(response.data["issues"][0]["creator"]["note"], [1, 2]) + # And : 이슈의 creator의 토픽의 값은 [1] 이어야 한다. + self.assertEqual(response.data["issues"][0]["creator"]["topic"], [3]) + + def test_issue_list_shoud_return_200_and_page_owner_is_list(self): + # Given: 1개의 콘텐츠 타입이 페이지인 이슈를 생성하고, cursor는 0, type은 [], mine은 false로 주어진다. + self._add_page_issue() + given_cursor = 0 + given_type = "[]" + given_mine = "false" + # When : API 실행 + response = self._call_api(given_cursor, given_type, given_mine) + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : 이슈의 creator의 노트의 값은 [1, 2] 이어야 한다. + self.assertEqual(response.data["issues"][0]["creator"]["note"], [1, 2]) + # And : 이슈의 creator의 토픽의 값은 [1] 이어야 한다. + self.assertEqual(response.data["issues"][0]["creator"]["topic"], [3]) + # And : 이슈의 creator의 페이지의 값은 [1, 2] 이어야 한다. + self.assertEqual(response.data["issues"][0]["creator"]["page"], [4, 5]) + + def test_issue_list_should_return_200_by_thirty_issue_list(self): + + # Given: 30개의 이슈를 생성하고, cursor는 0, type은 [], mine은 false로 주어진다. + self._add_issues(30) + given_cursor = 0 + given_type = "[]" + given_mine = "false" + # When : API 실행 + response = self._call_api(given_cursor, given_type, given_mine) + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : next_cursor는 30을 리턴한다. + self.assertEqual(response.data["next_cursor"], 30) + # And : 시작 cursor부터 30개의 이슈 리스트를 return 해야함. + self.assertEqual(len(response.data["issues"]), 30) + + def test_issue_list_should_return_200_by_ten_issue_list(self): + # Given: 10개의 이슈를 생성하고, cursor는 0, type은 [], mine은 false로 주어진다. + self._add_issues(10) + given_cursor = 5 + given_type = "[]" + given_mine = "false" + # When : API 실행 + response = self._call_api(given_cursor, given_type, given_mine) + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : next_cursor는 10을 리턴한다. + self.assertEqual(response.data["next_cursor"], 10) + # And : 시작 cursor부터 5개의 이슈 리스트를 return 해야함. + self.assertEqual(len(response.data["issues"]), 5) + + def test_issue_list_should_return_200_by_ten_issue_list_and_cursor_fifteen(self): + # Given: 10개의 이슈를 생성하고, cursor는 15로 주어진다. + self._add_issues(10) + given_cursor = 15 + given_type = "[]" + given_mine = "false" + # When : API 실행 + response = self._call_api(given_cursor, given_type, given_mine) + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : next_cursor는 10을 리턴한다. + self.assertEqual(response.data["next_cursor"], 10) + # And : 빈 배열을 return 해야함. + self.assertEqual(response.data["issues"], []) + + def test_issue_list_should_return_200_by_empty_issue_list(self): + # Given: 이슈 생성 없이, cursor는 0, type은 [], mine은 false로 주어진다. + given_cursor = 0 + given_type = "[]" + given_mine = "false" + # When : API 실행 + response = self._call_api(given_cursor, given_type, given_mine) + # Then : 상태코드 200 + self.assertEqual(response.status_code, status.HTTP_200_OK) + # And : next_cursor는 0, type은 [], mine은 false 리턴한다. + self.assertEqual(response.data["next_cursor"], 0) + # And : 빈 배열을 return 해야함. + self.assertEqual(response.data["issues"], []) From 6dc5963a1b71ebb5091263527a7915b5b84121f8 Mon Sep 17 00:00:00 2001 From: jingu Date: Wed, 1 Sep 2021 19:09:13 +0900 Subject: [PATCH 6/6] fix test code auth error --- v2/src/tests/test_issue.py | 67 +++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/v2/src/tests/test_issue.py b/v2/src/tests/test_issue.py index 541b321..750fd62 100644 --- a/v2/src/tests/test_issue.py +++ b/v2/src/tests/test_issue.py @@ -1,4 +1,5 @@ from ctrlf_auth.models import CtrlfUser +from ctrlf_auth.serializers import LoginSerializer from ctrlfbe.models import ( ContentRequest, CtrlfActionType, @@ -18,7 +19,11 @@ class TestIssueList(TestCase): def setUp(self): self.c = Client() - self.note_creator1 = CtrlfUser.objects.create_user(email="note_creator1@test.com", password="12345") + self.user_data = { + "email": "note_creator1@test.com", + "password": "12345", + } + self.note_creator1 = CtrlfUser.objects.create_user(**self.user_data) self.note_creator2 = CtrlfUser.objects.create_user(email="note_creator2@test.com", password="12345") self.topic_creator = CtrlfUser.objects.create_user(email="topic_creator@test.com", password="12345") self.page_creator1 = CtrlfUser.objects.create_user(email="page_creator1@test.com", password="12345") @@ -101,8 +106,16 @@ def _add_page_issue(self): issue = Issue.objects.create(**issue_data) return issue - def _call_api(self, cursor, type, mine): - return self.c.get(reverse("issues:issue_list"), {"cursor": cursor, "type": type, "mine": mine}) + def _call_api(self, cursor, type, mine, token=None): + if token: + header = {"HTTP_AUTHORIZATION": f"Bearer {token}"} + else: + header = {} + return self.c.get(reverse("issues:issue_list"), {"cursor": cursor, "type": type, "mine": mine}, **header) + + def _login(self): + serializer = LoginSerializer() + return serializer.validate(self.user_data)["token"] def test_issue_list_should_return_200(self): # Given: 2개의 이슈를 생성하고, cursor는 0, type은 [], mine은 false로 주어진다. @@ -110,8 +123,12 @@ def test_issue_list_should_return_200(self): given_cursor = 0 given_type = "[]" given_mine = "false" + + # And: 로그인해서 토큰을 발급받은 상황이다. + token = self._login() + # When : API 실행 - response = self._call_api(given_cursor, given_type, given_mine) + response = self._call_api(given_cursor, given_type, given_mine, token) # Then : 상태코드 200 self.assertEqual(response.status_code, status.HTTP_200_OK) # And : 첫번째 이슈의 title은 "test issue1" 이어야 한다. @@ -125,8 +142,12 @@ def test_issue_list_shoud_return_200_and_note_owner_is_list(self): given_cursor = 0 given_type = "[]" given_mine = "false" + + # And: 로그인해서 토큰을 발급받은 상황이다. + token = self._login() + # When : API 실행 - response = self._call_api(given_cursor, given_type, given_mine) + response = self._call_api(given_cursor, given_type, given_mine, token) # Then : 상태코드 200 self.assertEqual(response.status_code, status.HTTP_200_OK) # And : 이슈의 creator의 노트의 값은 [1, 2] 이어야 한다. @@ -138,8 +159,12 @@ def test_issue_list_shoud_return_200_and_topic_owner_is_list(self): given_cursor = 0 given_type = "[]" given_mine = "false" + + # And: 로그인해서 토큰을 발급받은 상황이다. + token = self._login() + # When : API 실행 - response = self._call_api(given_cursor, given_type, given_mine) + response = self._call_api(given_cursor, given_type, given_mine, token) # Then : 상태코드 200 self.assertEqual(response.status_code, status.HTTP_200_OK) # And : 이슈의 creator의 노트의 값은 [1, 2] 이어야 한다. @@ -153,8 +178,12 @@ def test_issue_list_shoud_return_200_and_page_owner_is_list(self): given_cursor = 0 given_type = "[]" given_mine = "false" + + # And: 로그인해서 토큰을 발급받은 상황이다. + token = self._login() + # When : API 실행 - response = self._call_api(given_cursor, given_type, given_mine) + response = self._call_api(given_cursor, given_type, given_mine, token) # Then : 상태코드 200 self.assertEqual(response.status_code, status.HTTP_200_OK) # And : 이슈의 creator의 노트의 값은 [1, 2] 이어야 한다. @@ -171,8 +200,12 @@ def test_issue_list_should_return_200_by_thirty_issue_list(self): given_cursor = 0 given_type = "[]" given_mine = "false" + + # And: 로그인해서 토큰을 발급받은 상황이다. + token = self._login() + # When : API 실행 - response = self._call_api(given_cursor, given_type, given_mine) + response = self._call_api(given_cursor, given_type, given_mine, token) # Then : 상태코드 200 self.assertEqual(response.status_code, status.HTTP_200_OK) # And : next_cursor는 30을 리턴한다. @@ -186,8 +219,12 @@ def test_issue_list_should_return_200_by_ten_issue_list(self): given_cursor = 5 given_type = "[]" given_mine = "false" + + # And: 로그인해서 토큰을 발급받은 상황이다. + token = self._login() + # When : API 실행 - response = self._call_api(given_cursor, given_type, given_mine) + response = self._call_api(given_cursor, given_type, given_mine, token) # Then : 상태코드 200 self.assertEqual(response.status_code, status.HTTP_200_OK) # And : next_cursor는 10을 리턴한다. @@ -201,8 +238,12 @@ def test_issue_list_should_return_200_by_ten_issue_list_and_cursor_fifteen(self) given_cursor = 15 given_type = "[]" given_mine = "false" + + # And: 로그인해서 토큰을 발급받은 상황이다. + token = self._login() + # When : API 실행 - response = self._call_api(given_cursor, given_type, given_mine) + response = self._call_api(given_cursor, given_type, given_mine, token) # Then : 상태코드 200 self.assertEqual(response.status_code, status.HTTP_200_OK) # And : next_cursor는 10을 리턴한다. @@ -215,8 +256,12 @@ def test_issue_list_should_return_200_by_empty_issue_list(self): given_cursor = 0 given_type = "[]" given_mine = "false" + + # And: 로그인해서 토큰을 발급받은 상황이다. + token = self._login() + # When : API 실행 - response = self._call_api(given_cursor, given_type, given_mine) + response = self._call_api(given_cursor, given_type, given_mine, token) # Then : 상태코드 200 self.assertEqual(response.status_code, status.HTTP_200_OK) # And : next_cursor는 0, type은 [], mine은 false 리턴한다.