diff --git a/.gitignore b/.gitignore index 649a584..c9c99b6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ source-score **coverage/ coverage.out test_database.db +.amazonq/ # ide .vscode/ diff --git a/acceptance/source_test.go b/acceptance/source_test.go index 6456b42..af8c062 100644 --- a/acceptance/source_test.go +++ b/acceptance/source_test.go @@ -50,6 +50,44 @@ var _ = Describe("Source model tests", func() { }) }) + When("POST request is sent with domainUrlNewsData field provided", func() { + It("should create source with domainUrlNewsData value", func() { + domainUrlNewsData := "example.com" + sourceInputWithDomain := api.SourceInput{ + Name: "Source with Domain", + Summary: "Source with domainUrlNewsData field", + Tags: "domain-test", + Uri: "https://source-with-domain", + DomainUrlNewsData: &domainUrlNewsData, + } + body, err := json.Marshal(sourceInputWithDomain) + Expect(err).To(BeNil()) + + resp, err := doRequest(http.MethodPost, endpoint, body) + Expect(err).To(BeNil()) + Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusCreated)) + + var respBody api.CreateSourceResponse + err = json.NewDecoder(resp.Body).Decode(&respBody) + Expect(err).To(BeNil()) + resp.Body.Close() + + By("verifying source was created with domainUrlNewsData") + srcUrl, err := url.JoinPath(endpoint, respBody.UriDigest) + Expect(err).To(BeNil()) + resp, err = doRequest(http.MethodGet, srcUrl, nil) + Expect(err).To(BeNil()) + defer resp.Body.Close() + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + + var source api.Source + err = json.NewDecoder(resp.Body).Decode(&source) + Expect(err).To(BeNil()) + Expect(source.DomainUrlNewsData).To(Equal(domainUrlNewsData)) + Expect(source.Name).To(Equal("Source with Domain")) + }) + }) + When("GET request is sent to query the created source", func() { It("should return the created source", func() { srcUrl, err := url.JoinPath(endpoint, uriDigest1) @@ -154,6 +192,42 @@ var _ = Describe("Source model tests", func() { }) }) + When("PATCH request is sent to update domainUrlNewsData field", func() { + It("should update the source's domainUrlNewsData value", func() { + updatedDomainUrlNewsData := "updated-domain.com" + updatedSrcInput := api.SourcePatchInput{ + DomainUrlNewsData: &updatedDomainUrlNewsData, + } + reqBody, err := json.Marshal(updatedSrcInput) + Expect(err).To(BeNil()) + + srcUrl, err := url.JoinPath(endpoint, uriDigest2) + Expect(err).To(BeNil()) + req, err := http.NewRequest(http.MethodPatch, srcUrl, bytes.NewBuffer(reqBody)) + Expect(err).To(BeNil()) + addCommonHeaders(req) + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode).To(Equal(http.StatusNoContent)) + resp.Body.Close() + + By("verifying domainUrlNewsData got updated") + var src api.Source + resp, err = doRequest(http.MethodGet, srcUrl, nil) + Expect(err).To(BeNil()) + defer resp.Body.Close() + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + + err = json.NewDecoder(resp.Body).Decode(&src) + Expect(err).To(BeNil()) + Expect(src.DomainUrlNewsData).To(Equal(updatedDomainUrlNewsData)) + Expect(src.Name).To(Equal("Sample Source 2")) + Expect(src.Summary).To(Equal("Sample summary 2")) + Expect(src.Tags).To(Equal("tag2")) + }) + }) + When("DELETE request is sent to delete the created source", func() { It("should delete the created source", func() { srcUrl, err := url.JoinPath(endpoint, uriDigest1) @@ -690,5 +764,59 @@ var _ = Describe("Source model tests", func() { }) }) + When("POST request with whitespace in domainUrlNewsData field value is sent", func() { + It("should return 400 Bad Request with nospace validation error message", func() { + domainUrlNewsDataWithSpace := "example com" + invalidInput := api.SourceInput{ + Name: "Valid Name", + Summary: "Valid Summary", + Tags: "tag1,tag2", + Uri: "https://example.com", + DomainUrlNewsData: &domainUrlNewsDataWithSpace, + } + body, _ := json.Marshal(invalidInput) + resp, err := doRequest(http.MethodPost, endpoint, body) + Expect(err).To(BeNil()) + defer resp.Body.Close() + Expect(resp.StatusCode).To(Equal(http.StatusBadRequest)) + + var errResp map[string]any + err = json.NewDecoder(resp.Body).Decode(&errResp) + Expect(err).To(BeNil()) + Expect(errResp["error"]).ToNot(BeNil()) + errorMsg := strings.ToLower(errResp["error"].(string)) + Expect(errorMsg).To(ContainSubstring("domainurlnewsdata validation failed")) + Expect(errorMsg).To(ContainSubstring("nospace")) + }) + }) + + When("PATCH request with empty string in domainUrlNewsData field value is sent", func() { + It("should return 400 Bad Request with nonempty validation error message", func() { + emptyDomainUrlNewsData := "" + invalidInput := api.SourcePatchInput{ + DomainUrlNewsData: &emptyDomainUrlNewsData, + } + body, _ := json.Marshal(invalidInput) + srcUrl, err := url.JoinPath(endpoint, uriDigest2) + Expect(err).To(BeNil()) + req, err := http.NewRequest(http.MethodPatch, srcUrl, bytes.NewBuffer(body)) + Expect(err).To(BeNil()) + addCommonHeaders(req) + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + Expect(err).To(BeNil()) + defer resp.Body.Close() + Expect(resp.StatusCode).To(Equal(http.StatusBadRequest)) + + var errResp map[string]any + err = json.NewDecoder(resp.Body).Decode(&errResp) + Expect(err).To(BeNil()) + Expect(errResp["error"]).ToNot(BeNil()) + errorMsg := strings.ToLower(errResp["error"].(string)) + Expect(errorMsg).To(ContainSubstring("domainurlnewsdata validation failed")) + Expect(errorMsg).To(ContainSubstring("nonempty")) + }) + }) + }) }) diff --git a/api/source-score.yaml b/api/source-score.yaml index cebe347..261d16f 100644 --- a/api/source-score.yaml +++ b/api/source-score.yaml @@ -486,6 +486,12 @@ components: x-oapi-codegen-extra-tags: validate: httpsurl binding: required + domainUrlNewsData: + type: string + description: Domain url corresponding to newsdata.io `domainurl` parameter + example: "timesofindia.indiatimes.com,theguardian.com" + x-oapi-codegen-extra-tags: + validate: omitnil,nonempty,nospace SourcePatchInput: type: object @@ -512,6 +518,12 @@ components: example: "news,journalism,trusted" x-oapi-codegen-extra-tags: validate: omitnil,nospace,nonempty + domainUrlNewsData: + type: string + description: Domain url corresponding to newsdata.io `domainurl` parameter + example: "timesofindia.indiatimes.com,theguardian.com" + x-oapi-codegen-extra-tags: + validate: omitnil,nonempty,nospace Source: type: object @@ -523,6 +535,7 @@ components: - tags - uri - uriDigest + - domainUrlNewsData properties: name: type: string @@ -562,6 +575,12 @@ components: example: "https://www.nytimes.com" x-oapi-codegen-extra-tags: binding: required + domainUrlNewsData: + type: string + description: Domain url corresponding to newsdata.io `domainurl` parameter + example: "timesofindia.indiatimes.com,theguardian.com" + x-oapi-codegen-extra-tags: + binding: required CreateSourceResponse: type: object diff --git a/pkg/api/server.gen.go b/pkg/api/server.gen.go index ba4fbcd..0f99252 100644 --- a/pkg/api/server.gen.go +++ b/pkg/api/server.gen.go @@ -116,6 +116,9 @@ type ProofPatchInput struct { // Source Complete source entity with calculated credibility score type Source struct { + // DomainUrlNewsData Domain url corresponding to newsdata.io `domainurl` parameter + DomainUrlNewsData string `binding:"required" json:"domainUrlNewsData"` + // Name Display name of the source Name string `binding:"required" json:"name"` @@ -137,6 +140,9 @@ type Source struct { // SourceInput Input schema for creating a new source type SourceInput struct { + // DomainUrlNewsData Domain url corresponding to newsdata.io `domainurl` parameter + DomainUrlNewsData *string `json:"domainUrlNewsData,omitempty" validate:"omitnil,nonempty,nospace"` + // Name Display name of the information source Name string `binding:"required" json:"name" validate:"nonempty"` @@ -152,6 +158,9 @@ type SourceInput struct { // SourcePatchInput Input schema for partially updating a source type SourcePatchInput struct { + // DomainUrlNewsData Domain url corresponding to newsdata.io `domainurl` parameter + DomainUrlNewsData *string `json:"domainUrlNewsData,omitempty" validate:"omitnil,nonempty,nospace"` + // Name Updated display name Name *string `json:"name" validate:"omitnil,nonempty"` diff --git a/pkg/domain/source/source_repository.go b/pkg/domain/source/source_repository.go index 73ca23c..794948c 100644 --- a/pkg/domain/source/source_repository.go +++ b/pkg/domain/source/source_repository.go @@ -81,6 +81,10 @@ func (sr *sourceRepository) PostSource(ctx context.Context, sourceInput *api.Sou UriDigest: uriDigest, } + if sourceInput.DomainUrlNewsData != nil { + source.DomainUrlNewsData = *sourceInput.DomainUrlNewsData + } + result := sr.client.Create(ctx, source) slog.InfoContext( ctx, @@ -113,6 +117,9 @@ func (sr *sourceRepository) PatchSourceByUriDigest(ctx context.Context, sourceIn if sourceInput.Tags != nil { source.Tags = *sourceInput.Tags } + if sourceInput.DomainUrlNewsData != nil { + source.DomainUrlNewsData = *sourceInput.DomainUrlNewsData + } result = sr.client.Update(ctx, source) slog.InfoContext( diff --git a/pkg/domain/source/source_repository_test.go b/pkg/domain/source/source_repository_test.go index 9e4d73f..3e0a311 100644 --- a/pkg/domain/source/source_repository_test.go +++ b/pkg/domain/source/source_repository_test.go @@ -54,10 +54,12 @@ var _ = Describe("Source model repository layer unit tests", Ordered, func() { name := "Updated Sample Source 1" summary := "Updated Sample summary" tags := "updated-tag1" + domainUrlNewsData := "source1.com" sourceInput := &api.SourcePatchInput{ Name: &name, Summary: &summary, Tags: &tags, + DomainUrlNewsData: &domainUrlNewsData, } err := sourceRepo.PatchSourceByUriDigest(context.TODO(), sourceInput, uriDigest1) @@ -70,6 +72,7 @@ var _ = Describe("Source model repository layer unit tests", Ordered, func() { Expect(source.Tags).To(BeEquivalentTo(*sourceInput.Tags)) Expect(source.Uri).To(BeEquivalentTo(sampleSourceInput1.Uri)) Expect(source.UriDigest).To(BeEquivalentTo(uriDigest1)) + Expect(source.DomainUrlNewsData).To(BeEquivalentTo(domainUrlNewsData)) }) }) diff --git a/pkg/domain/source/source_service_test.go b/pkg/domain/source/source_service_test.go index b2f5f0d..e4bb149 100644 --- a/pkg/domain/source/source_service_test.go +++ b/pkg/domain/source/source_service_test.go @@ -56,15 +56,18 @@ var _ = Describe("Source model service layer unit test", Ordered, func() { name := "Updated Sample Source 1" summary := "Updated Sample summary" tags := "updated-tag1" + domainUrlNewsData := "source1.com" sourceInput := &api.SourcePatchInput{ - Name: &name, - Summary: &summary, - Tags: &tags, + Name: &name, + Summary: &summary, + Tags: &tags, + DomainUrlNewsData: &domainUrlNewsData, } updatedSource = sampleSource1 updatedSource.Name = "Updated Sample Source 1" updatedSource.Summary = "Updated Sample summary" updatedSource.Tags = "updated-tag1" + updatedSource.DomainUrlNewsData = domainUrlNewsData fakeSourceRepo.GetSourceByUriDigestReturnsOnCall(1, &updatedSource, nil) err := sourceSvc.PatchSourceByUriDigest(context.TODO(), sourceInput, uriDigest1) @@ -83,6 +86,7 @@ var _ = Describe("Source model service layer unit test", Ordered, func() { Expect(source.Tags).To(BeEquivalentTo(*sourceInput.Tags)) Expect(source.Uri).To(BeEquivalentTo(sampleSourceInput1.Uri)) Expect(source.UriDigest).To(BeEquivalentTo(uriDigest1)) + Expect(source.DomainUrlNewsData).To(BeEquivalentTo(domainUrlNewsData)) }) }) @@ -300,6 +304,38 @@ var _ = Describe("Source model service layer unit test", Ordered, func() { Expect(strings.ToLower(err.Error())).To(ContainSubstring("nonempty")) }) }) + + When("Patching a source's DomainUrlNewsData field with empty string value", func() { + It("Should return invalid source error with nonempty validation message", func() { + emptyDomainUrlNewsData := "" + invalidInput := &api.SourcePatchInput{ + DomainUrlNewsData: &emptyDomainUrlNewsData, + } + + err := sourceSvc.PatchSourceByUriDigest(context.TODO(), invalidInput, uriDigest1) + + Expect(err).ToNot(BeNil()) + Expect(errors.Is(err, apperrors.ErrInvalidSource)).To(BeTrue()) + Expect(strings.ToLower(err.Error())).To(ContainSubstring("domainurlnewsdata validation failed")) + Expect(strings.ToLower(err.Error())).To(ContainSubstring("nonempty")) + }) + }) + + When("Patching a source's DomainUrlNewsData field with a string containing spaces", func() { + It("Should return invalid source error with nospace validation message", func() { + domainUrlNewsDataWithSpaces := "example com" + invalidInput := &api.SourcePatchInput{ + DomainUrlNewsData: &domainUrlNewsDataWithSpaces, + } + + err := sourceSvc.PatchSourceByUriDigest(context.TODO(), invalidInput, uriDigest1) + + Expect(err).ToNot(BeNil()) + Expect(errors.Is(err, apperrors.ErrInvalidSource)).To(BeTrue()) + Expect(strings.ToLower(err.Error())).To(ContainSubstring("domainurlnewsdata validation failed")) + Expect(strings.ToLower(err.Error())).To(ContainSubstring("nospace")) + }) + }) }) Context("Source GET validation tests", func() {