From 713774f2d18985ba7c1c3441719da897a34c9030 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Tue, 12 May 2026 22:46:02 +0530 Subject: [PATCH 1/3] feat: adds a non required domainUrlNewsData field to source model Signed-off-by: Amit Singh --- api/source-score.yaml | 22 ++++++++++++++++++++++ pkg/api/server.gen.go | 9 +++++++++ pkg/domain/source/source_repository.go | 3 +++ 3 files changed, 34 insertions(+) diff --git a/api/source-score.yaml b/api/source-score.yaml index cebe347..0456899 100644 --- a/api/source-score.yaml +++ b/api/source-score.yaml @@ -486,6 +486,13 @@ 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: nonempty,nospace + binding: required SourcePatchInput: type: object @@ -512,6 +519,13 @@ 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: nonempty,nospace + binding: required Source: type: object @@ -523,6 +537,7 @@ components: - tags - uri - uriDigest + - domainUrlNewsData properties: name: type: string @@ -562,6 +577,13 @@ 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: + validate: nonempty,nospace + binding: required CreateSourceResponse: type: object diff --git a/pkg/api/server.gen.go b/pkg/api/server.gen.go index ba4fbcd..386c9ee 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" validate:"nonempty,nospace"` + // 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 `binding:"required" json:"domainUrlNewsData,omitempty" validate:"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 `binding:"required" json:"domainUrlNewsData,omitempty" validate:"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..e2aed24 100644 --- a/pkg/domain/source/source_repository.go +++ b/pkg/domain/source/source_repository.go @@ -113,6 +113,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( From 11311b38405fab5687a1a31bbdf3db5032a7a089 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Wed, 13 May 2026 09:43:47 +0530 Subject: [PATCH 2/3] feat: adds unit tests for the domainUrlNewsData field Signed-off-by: Amit Singh --- .gitignore | 1 + api/source-score.yaml | 5 ++- pkg/api/server.gen.go | 6 ++-- pkg/domain/source/source_repository_test.go | 2 ++ pkg/domain/source/source_service_test.go | 34 +++++++++++++++++++++ 5 files changed, 42 insertions(+), 6 deletions(-) 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/api/source-score.yaml b/api/source-score.yaml index 0456899..91c5342 100644 --- a/api/source-score.yaml +++ b/api/source-score.yaml @@ -491,7 +491,7 @@ components: description: Domain url corresponding to newsdata.io `domainurl` parameter example: "timesofindia.indiatimes.com,theguardian.com" x-oapi-codegen-extra-tags: - validate: nonempty,nospace + validate: omitnil,nonempty,nospace binding: required SourcePatchInput: @@ -524,7 +524,7 @@ components: description: Domain url corresponding to newsdata.io `domainurl` parameter example: "timesofindia.indiatimes.com,theguardian.com" x-oapi-codegen-extra-tags: - validate: nonempty,nospace + validate: omitnil,nonempty,nospace binding: required Source: @@ -582,7 +582,6 @@ components: description: Domain url corresponding to newsdata.io `domainurl` parameter example: "timesofindia.indiatimes.com,theguardian.com" x-oapi-codegen-extra-tags: - validate: nonempty,nospace binding: required CreateSourceResponse: diff --git a/pkg/api/server.gen.go b/pkg/api/server.gen.go index 386c9ee..b7e2870 100644 --- a/pkg/api/server.gen.go +++ b/pkg/api/server.gen.go @@ -117,7 +117,7 @@ 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" validate:"nonempty,nospace"` + DomainUrlNewsData string `binding:"required" json:"domainUrlNewsData"` // Name Display name of the source Name string `binding:"required" json:"name"` @@ -141,7 +141,7 @@ 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 `binding:"required" json:"domainUrlNewsData,omitempty" validate:"nonempty,nospace"` + DomainUrlNewsData *string `binding:"required" json:"domainUrlNewsData,omitempty" validate:"omitnil,nonempty,nospace"` // Name Display name of the information source Name string `binding:"required" json:"name" validate:"nonempty"` @@ -159,7 +159,7 @@ 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 `binding:"required" json:"domainUrlNewsData,omitempty" validate:"nonempty,nospace"` + DomainUrlNewsData *string `binding:"required" 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_test.go b/pkg/domain/source/source_repository_test.go index 9e4d73f..5e8baa3 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) diff --git a/pkg/domain/source/source_service_test.go b/pkg/domain/source/source_service_test.go index b2f5f0d..9b19846 100644 --- a/pkg/domain/source/source_service_test.go +++ b/pkg/domain/source/source_service_test.go @@ -56,10 +56,12 @@ 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, + DomainUrlNewsData: &domainUrlNewsData, } updatedSource = sampleSource1 updatedSource.Name = "Updated Sample Source 1" @@ -300,6 +302,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() { From 8bac42bb9a38c121603e9a9ae1494e4c05369848 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Wed, 13 May 2026 10:06:44 +0530 Subject: [PATCH 3/3] test: adds ac tests for DomainUrlNewsData field Signed-off-by: Amit Singh --- acceptance/source_test.go | 128 ++++++++++++++++++++ api/source-score.yaml | 2 - pkg/api/server.gen.go | 4 +- pkg/domain/source/source_repository.go | 4 + pkg/domain/source/source_repository_test.go | 1 + pkg/domain/source/source_service_test.go | 8 +- 6 files changed, 140 insertions(+), 7 deletions(-) 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 91c5342..261d16f 100644 --- a/api/source-score.yaml +++ b/api/source-score.yaml @@ -492,7 +492,6 @@ components: example: "timesofindia.indiatimes.com,theguardian.com" x-oapi-codegen-extra-tags: validate: omitnil,nonempty,nospace - binding: required SourcePatchInput: type: object @@ -525,7 +524,6 @@ components: example: "timesofindia.indiatimes.com,theguardian.com" x-oapi-codegen-extra-tags: validate: omitnil,nonempty,nospace - binding: required Source: type: object diff --git a/pkg/api/server.gen.go b/pkg/api/server.gen.go index b7e2870..0f99252 100644 --- a/pkg/api/server.gen.go +++ b/pkg/api/server.gen.go @@ -141,7 +141,7 @@ 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 `binding:"required" json:"domainUrlNewsData,omitempty" validate:"omitnil,nonempty,nospace"` + DomainUrlNewsData *string `json:"domainUrlNewsData,omitempty" validate:"omitnil,nonempty,nospace"` // Name Display name of the information source Name string `binding:"required" json:"name" validate:"nonempty"` @@ -159,7 +159,7 @@ 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 `binding:"required" json:"domainUrlNewsData,omitempty" validate:"omitnil,nonempty,nospace"` + 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 e2aed24..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, diff --git a/pkg/domain/source/source_repository_test.go b/pkg/domain/source/source_repository_test.go index 5e8baa3..3e0a311 100644 --- a/pkg/domain/source/source_repository_test.go +++ b/pkg/domain/source/source_repository_test.go @@ -72,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 9b19846..e4bb149 100644 --- a/pkg/domain/source/source_service_test.go +++ b/pkg/domain/source/source_service_test.go @@ -58,15 +58,16 @@ var _ = Describe("Source model service layer unit test", Ordered, func() { 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) @@ -85,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)) }) })