Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions acceptance/claim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,29 @@ var _ = Describe("Claim model tests", func() {
})

Context("Validation tests", func() {
When("posting a claim that already exists", func() {
It("should return 409 Conflict with error message", func() {
dupClaim := api.ClaimInput{
SourceUriDigest: uriDigest3,
Summary: sampleClaim2.Summary,
Title: sampleClaim2.Title,
Uri: sampleClaim2.Uri,
}
body, err := json.Marshal(dupClaim)
Expect(err).To(BeNil())

resp, err := doRequest(http.MethodPost, endpoint, body)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusConflict))

var errResp map[string]any
err = json.NewDecoder(resp.Body).Decode(&errResp)
Expect(err).To(BeNil())
Expect(errResp["error"]).ToNot(BeNil())
Expect(strings.Contains(strings.ToLower(errResp["error"].(string)), "claim already exists")).To(BeTrue())
})
})
When("POST request with empty sourceUriDigest is sent", func() {
It("should return 400 with validation error", func() {
claim := api.ClaimInput{
Expand Down
23 changes: 23 additions & 0 deletions acceptance/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,29 @@ var _ = Describe("Proof model tests", func() {
})

Context("Validation tests", func() {
When("posting a proof that already exists", func() {
It("should return 409 Conflict with error message", func() {
dupProof := api.ProofInput{
ClaimUriDigest: claim3Digest,
ReviewedBy: sampleProof2.ReviewedBy,
SupportsClaim: &sampleProof2.SupportsClaim,
Uri: sampleProof2.Uri,
}
body, err := json.Marshal(dupProof)
Expect(err).To(BeNil())

resp, err := doRequest(http.MethodPost, endpoint, body)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusConflict))

var errResp map[string]any
err = json.NewDecoder(resp.Body).Decode(&errResp)
Expect(err).To(BeNil())
Expect(errResp["error"]).ToNot(BeNil())
Expect(strings.Contains(strings.ToLower(errResp["error"].(string)), "proof already exists")).To(BeTrue())
})
})
When("POST request with space in ClaimUriDigest is sent", func() {
It("should return 400 with validation error mentioning nospace", func() {
supports := true
Expand Down
19 changes: 19 additions & 0 deletions acceptance/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,25 @@ var _ = Describe("Source model tests", func() {
})

Context("Validation tests", func() {
When("posting a source that already exists", func() {
It("should return 409 Conflict with error message", func() {
// Try to create sourceInput2 again (already created above)
dupBody, err := json.Marshal(sourceInput2)
Expect(err).To(BeNil())

resp, err := doRequest(http.MethodPost, endpoint, dupBody)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(http.StatusConflict))

var errResp map[string]any
err = json.NewDecoder(resp.Body).Decode(&errResp)
Expect(err).To(BeNil())
Expect(errResp["error"]).ToNot(BeNil())
Expect(strings.Contains(strings.ToLower(errResp["error"].(string)), "source already exists")).To(BeTrue())
})
})

When("GET request is sent for an invalid source", func() {
It("should return 404 error", func() {
srcUrl, err := url.JoinPath(endpoint, "invalid-digest")
Expand Down
5 changes: 2 additions & 3 deletions cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import (
"os"
embed "source-score"

"github.com/gin-gonic/gin"
"github.com/gin-contrib/cors"
"gorm.io/gorm"
"github.com/gin-gonic/gin"

"source-score/pkg/api"
"source-score/pkg/conf"
Expand Down Expand Up @@ -57,7 +56,7 @@ func main() {
conf.Cfg.AppUserPassword,
conf.DbName,
)
dbClient := pgsql.NewClient(context.Background(), dsn, &gorm.Config{})
dbClient := pgsql.NewClient(context.Background(), dsn, conf.GormConfig)
// TODO: wrap this and call it securely
dbClient.SetAutoMigration(
context.Background(),
Expand Down
3 changes: 3 additions & 0 deletions pkg/apperrors/apperrors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ var (
ErrProofNotFound = errors.New("proof not found")
ErrValidationLogic = errors.New("validation logic error")
ErrInvalidClaimVerification = errors.New("invalid claim verification body")
ErrDuplicateSource = errors.New("source already exists")
ErrDuplicateClaim = errors.New("claim already exists")
ErrDuplicateProof = errors.New("proof already exists")
)
9 changes: 8 additions & 1 deletion pkg/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

"github.com/ilyakaznacheev/cleanenv"
"gorm.io/gorm"
)

const (
Expand All @@ -19,7 +20,13 @@ type conf struct {
SuperUserPassword string `env:"SUPER_USER_PASSWORD" yaml:"SUPER_USER_PASSWORD" env-required:"true"`
}

var Cfg conf
var (
Cfg conf

GormConfig = &gorm.Config{
TranslateError: true,
}
)

func LoadConfig() {
if envPath, ok := os.LookupEnv("DOTENV_PATH"); ok {
Expand Down
15 changes: 15 additions & 0 deletions pkg/domain/claim/claim_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,21 @@ var _ = Describe("Claim repository layer unit tests", func() {
})

Context("Validation tests", func() {
When("Creating a claim that already exists", func() {
It("Should return gorm.ErrDuplicatedKey", func() {
input := api.ClaimInput{
SourceUriDigest: sampleClaim2.SourceUriDigest,
Summary: sampleClaim2.Summary,
Title: sampleClaim2.Title,
Uri: sampleClaim2.Uri,
}

_, err := claimRepo.PostClaim(context.TODO(), &input)
Expect(err).To(HaveOccurred())
Expect(errors.Is(err, gorm.ErrDuplicatedKey)).To(BeTrue())
})
})

When("Retrieving a non-existent claim by uri digest", func() {
It("Should return gorm.ErrRecordNotFound", func() {
_, err := claimRepo.GetClaimByUriDigest(context.TODO(), "doesnotexist")
Expand Down
11 changes: 10 additions & 1 deletion pkg/domain/claim/claim_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,16 @@ func (svc *claimService) PostClaim(ctx context.Context, claimInput *api.ClaimInp
return "", fmt.Errorf("%w: %s", apperrors.ErrInvalidClaim, combinedErrs)
}

return svc.claimRepo.PostClaim(ctx, claimInput)
digest, err := svc.claimRepo.PostClaim(ctx, claimInput)
if err != nil {
switch {
case errors.Is(err, gorm.ErrDuplicatedKey):
return "", apperrors.ErrDuplicateClaim
default:
return "", err
}
}
return digest, nil
}

func (svc *claimService) VerifyClaimByUriDigest(ctx context.Context, claimVerification *api.ClaimVerification, uriDigest string) error {
Expand Down
18 changes: 18 additions & 0 deletions pkg/domain/claim/claim_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,24 @@ var _ = Describe("Claim model service layer unit tests", Ordered, func() {
})

Context("Validation tests", func() {
When("Posting a claim that already exists", func() {
It("Should return ErrDuplicateClaim", func() {
callCount := fakeClaimRepo.PostClaimCallCount()
fakeClaimRepo.PostClaimReturnsOnCall(callCount, "", gorm.ErrDuplicatedKey)

input := api.ClaimInput{
SourceUriDigest: sampleClaim2.SourceUriDigest,
Summary: sampleClaim2.Summary,
Title: sampleClaim2.Title,
Uri: sampleClaim2.Uri,
}

_, err := claimSvc.PostClaim(context.TODO(), &input)
Expect(err).ToNot(BeNil())
Expect(errors.Is(err, apperrors.ErrDuplicateClaim)).To(BeTrue())
})
})

When("Posting a claim with empty source uri digest", func() {
It("Should return ErrInvalidClaim", func() {
input := api.ClaimInput{
Expand Down
6 changes: 5 additions & 1 deletion pkg/domain/claim/claim_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package claim_test
import (
"context"
"source-score/pkg/api"
"source-score/pkg/conf"
"source-score/pkg/db/pgsql"
"source-score/pkg/domain/claim"
"source-score/pkg/helpers"
Expand Down Expand Up @@ -59,7 +60,10 @@ var (

func TestClaim(t *testing.T) {
var _ = BeforeSuite(func() {
testDB, err = gorm.Open(sqlite.Open(testDBFile))
testDB, err = gorm.Open(
sqlite.Open(testDBFile),
conf.GormConfig,
)
Expect(err).ToNot(HaveOccurred())

err = testDB.AutoMigrate(&api.Source{}, &api.Claim{}, &api.Proof{})
Expand Down
14 changes: 14 additions & 0 deletions pkg/domain/proof/proof_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ var _ = Describe("Proof repository layer unit tests", Ordered, func() {
})

Context("Validation tests", Ordered, func() {
When("Creating a proof that already exists", func() {
It("Should return gorm.ErrDuplicatedKey", func() {
proofInput := &api.ProofInput{
ClaimUriDigest: sampleProof2.ClaimUriDigest,
Uri: sampleProof2.Uri,
SupportsClaim: &sampleProof2.SupportsClaim,
ReviewedBy: sampleProof2.ReviewedBy,
}
_, err := proofRepo.PostProof(context.TODO(), proofInput)
Expect(err).To(HaveOccurred())
Expect(errors.Is(err, gorm.ErrDuplicatedKey)).To(BeTrue())
})
})

When("Retrieving a non-existent proof by uri digest", func() {
It("Should return gorm.ErrRecordNotFound", func() {
_, err := proofRepo.GetProofByUriDigest(context.TODO(), "doesnotexist")
Expand Down
12 changes: 11 additions & 1 deletion pkg/domain/proof/proof_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,17 @@ func (svc *proofService) PostProof(ctx context.Context, proofInput *api.ProofInp
return "", fmt.Errorf("%w: %s", apperrors.ErrInvalidProof, combinedErrs)
}

return svc.proofRepo.PostProof(ctx, proofInput)
digest, err := svc.proofRepo.PostProof(ctx, proofInput)
if err != nil {
switch {
case errors.Is(err, gorm.ErrDuplicatedKey):
return "", apperrors.ErrDuplicateProof
default:
return "", err
}
}

return digest, nil
}

func (svc *proofService) GetProofsByClaims(ctx context.Context) (map[string][]api.Proof, error) {
Expand Down
18 changes: 18 additions & 0 deletions pkg/domain/proof/proof_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,24 @@ var _ = Describe("Proof model service layer unit tests", Ordered, func() {
})

Context("Proof POST validation tests", func() {
When("Posting a proof that already exists", func() {
It("Should return ErrDuplicateProof", func() {
callCount := fakeProofRepo.PostProofCallCount()
fakeProofRepo.PostProofReturnsOnCall(callCount, "", gorm.ErrDuplicatedKey)

input := api.ProofInput{
ClaimUriDigest: sampleProof2.ClaimUriDigest,
ReviewedBy: sampleProof2.ReviewedBy,
SupportsClaim: &sampleProof2.SupportsClaim,
Uri: sampleProof2.Uri,
}

_, err := proofSvc.PostProof(context.TODO(), &input)
Expect(err).ToNot(BeNil())
Expect(errors.Is(err, apperrors.ErrDuplicateProof)).To(BeTrue())
})
})

When("Posting a proof with space in ClaimUriDigest", func() {
It("Should return invalid proof error with nospace validation message", func() {
supports := true
Expand Down
6 changes: 5 additions & 1 deletion pkg/domain/proof/proof_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package proof_test
import (
"context"
"source-score/pkg/api"
"source-score/pkg/conf"
"source-score/pkg/db/pgsql"
"source-score/pkg/domain/proof"
"source-score/pkg/helpers"
Expand Down Expand Up @@ -55,7 +56,10 @@ var (

func TestProof(t *testing.T) {
var _ = BeforeSuite(func() {
testDB, err = gorm.Open(sqlite.Open(testDBFile))
testDB, err = gorm.Open(
sqlite.Open(testDBFile),
conf.GormConfig,
)
Expect(err).ToNot(HaveOccurred())

err = testDB.AutoMigrate(&api.Source{}, &api.Claim{}, &api.Proof{})
Expand Down
7 changes: 7 additions & 0 deletions pkg/domain/source/source_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ var _ = Describe("Source model repository layer unit tests", Ordered, func() {
})

Context("Validation tests", func() {
When("Creating a source that already exists", func() {
It("Should return a duplicate record error", func() {
_, err := sourceRepo.PostSource(context.TODO(), &sampleSourceInput2)
Expect(err).To(HaveOccurred())
Expect(errors.Is(err, gorm.ErrDuplicatedKey)).To(BeTrue())
})
})
When("Patching a source that does not exist", func() {
It("Should return record not found error", func() {
name := "Twice Updated Sample Source 1"
Expand Down
13 changes: 12 additions & 1 deletion pkg/domain/source/source_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,18 @@ func (svc *sourceService) PostSource(ctx context.Context, sourceInput *api.Sourc
combinedErrs = strings.TrimSpace(combinedErrs)
return "", fmt.Errorf("%w: %s", apperrors.ErrInvalidSource, combinedErrs)
}
return svc.sourceRepo.PostSource(ctx, sourceInput)

digest, err := svc.sourceRepo.PostSource(ctx, sourceInput)
if err != nil {
switch {
case errors.Is(err, gorm.ErrDuplicatedKey):
return "", apperrors.ErrDuplicateSource
default:
return "", err
}
}

return digest, nil
}

func (svc *sourceService) PatchSourceByUriDigest(ctx context.Context, sourceInput *api.SourcePatchInput, uriDigest string) error {
Expand Down
11 changes: 11 additions & 0 deletions pkg/domain/source/source_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,17 @@ var _ = Describe("Source model service layer unit test", Ordered, func() {
})

Context("Source POST validation tests", func() {
When("Creating a source that already exists", func() {
It("Should return duplicate source error", func() {
callCount := fakeSourceRepo.PostSourceCallCount()
fakeSourceRepo.PostSourceReturnsOnCall(callCount, "", gorm.ErrDuplicatedKey)

_, err := sourceSvc.PostSource(context.TODO(), &sampleSourceInput1)
Expect(err).ToNot(BeNil())
Expect(errors.Is(err, apperrors.ErrDuplicateSource)).To(BeTrue())
})
})

When("Creating a source with tags containing spaces", func() {
It("Should return invalid source error with nospace validation message", func() {
invalidInput := &api.SourceInput{
Expand Down
6 changes: 5 additions & 1 deletion pkg/domain/source/source_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"log"
"source-score/pkg/api"
"source-score/pkg/conf"
"source-score/pkg/db/pgsql"
"source-score/pkg/domain/claim/claimfakes"
"source-score/pkg/domain/source"
Expand Down Expand Up @@ -42,7 +43,10 @@ var (

func TestSource(t *testing.T) {
var _ = BeforeSuite(func() {
testDB, err = gorm.Open(sqlite.Open(testDBFile))
testDB, err = gorm.Open(
sqlite.Open(testDBFile),
conf.GormConfig,
)
Expect(err).ToNot(HaveOccurred())

err = testDB.AutoMigrate(&api.Source{}, &api.SourceInput{})
Expand Down
5 changes: 5 additions & 0 deletions pkg/handlers/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ func (ch *ClaimHandler) PostClaim(ctx *gin.Context) {
http.StatusBadRequest,
gin.H{"error": err.Error()},
)
case errors.Is(err, apperrors.ErrDuplicateClaim):
ctx.JSON(
http.StatusConflict,
gin.H{"error": err.Error()},
)
default:
slog.Error("failed to create claim", "error", err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
Expand Down
2 changes: 2 additions & 0 deletions pkg/handlers/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func (ph *ProofHandler) PostProof(ctx *gin.Context) {
switch {
case errors.Is(err, apperrors.ErrInvalidProof):
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
case errors.Is(err, apperrors.ErrDuplicateProof):
ctx.JSON(http.StatusConflict, gin.H{"error": err.Error()})
default:
slog.Error("failed to create proof", "error", err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
Expand Down
Loading
Loading