Compare commits
5 Commits
fd0fd8954a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 65109a787c | |||
| 75f1406edf | |||
| 11dc9b507f | |||
| da616438e9 | |||
| 7486865343 |
@@ -21,6 +21,8 @@ var (
|
||||
seedRandOnce sync.Once
|
||||
)
|
||||
|
||||
const testPasswordHash = "test_password_hash"
|
||||
|
||||
func initSeedRand() {
|
||||
seedRandOnce.Do(func() {
|
||||
seed := time.Now().UnixNano()
|
||||
@@ -61,11 +63,11 @@ func TestSeedCommand(t *testing.T) {
|
||||
seedUserCount := 0
|
||||
var seedUser *database.User
|
||||
regularUserCount := 0
|
||||
for i := range users {
|
||||
if users[i].Username == "seed_admin" {
|
||||
for idx := range users {
|
||||
if users[idx].Username == seedUsername {
|
||||
seedUserCount++
|
||||
seedUser = &users[i]
|
||||
} else if strings.HasPrefix(users[i].Username, "user_") {
|
||||
seedUser = &users[idx]
|
||||
} else if strings.HasPrefix(users[idx].Username, "user_") {
|
||||
regularUserCount++
|
||||
}
|
||||
}
|
||||
@@ -78,12 +80,12 @@ func TestSeedCommand(t *testing.T) {
|
||||
t.Fatal("Expected seed user to be created")
|
||||
}
|
||||
|
||||
if seedUser.Username != "seed_admin" {
|
||||
t.Errorf("Expected username to be 'seed_admin', got '%s'", seedUser.Username)
|
||||
if seedUser.Username != seedUsername {
|
||||
t.Errorf("Expected username to be %q, got '%s'", seedUsername, seedUser.Username)
|
||||
}
|
||||
|
||||
if seedUser.Email != "seed_admin@goyco.local" {
|
||||
t.Errorf("Expected email to be 'seed_admin@goyco.local', got '%s'", seedUser.Email)
|
||||
if seedUser.Email != seedEmail {
|
||||
t.Errorf("Expected email to be %q, got '%s'", seedEmail, seedUser.Email)
|
||||
}
|
||||
|
||||
if !seedUser.EmailVerified {
|
||||
@@ -103,20 +105,20 @@ func TestSeedCommand(t *testing.T) {
|
||||
t.Errorf("Expected 5 posts, got %d", len(posts))
|
||||
}
|
||||
|
||||
for i, post := range posts {
|
||||
for idx, post := range posts {
|
||||
if post.Title == "" {
|
||||
t.Errorf("Post %d has empty title", i)
|
||||
t.Errorf("Post %d has empty title", idx)
|
||||
}
|
||||
if post.URL == "" {
|
||||
t.Errorf("Post %d has empty URL", i)
|
||||
t.Errorf("Post %d has empty URL", idx)
|
||||
}
|
||||
if post.AuthorID == nil || *post.AuthorID != seedUser.ID {
|
||||
t.Errorf("Post %d has wrong author ID: expected %d, got %v", i, seedUser.ID, post.AuthorID)
|
||||
t.Errorf("Post %d has wrong author ID: expected %d, got %v", idx, seedUser.ID, post.AuthorID)
|
||||
}
|
||||
|
||||
expectedScore := post.UpVotes - post.DownVotes
|
||||
if post.Score != expectedScore {
|
||||
t.Errorf("Post %d has incorrect score: expected %d, got %d", i, expectedScore, post.Score)
|
||||
t.Errorf("Post %d has incorrect score: expected %d, got %d", idx, expectedScore, post.Score)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,11 +150,12 @@ func TestSeedCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateRandomPath(t *testing.T) {
|
||||
const articlePathPrefix = "/article/"
|
||||
initSeedRand()
|
||||
pathLength := seedRandSource.Intn(20)
|
||||
path := "/article/"
|
||||
path := articlePathPrefix
|
||||
|
||||
for i := 0; i < pathLength+5; i++ {
|
||||
for idx := 0; idx < pathLength+5; idx++ {
|
||||
randomChar := seedRandSource.Intn(26)
|
||||
path += string(rune('a' + randomChar))
|
||||
}
|
||||
@@ -167,13 +170,14 @@ func TestGenerateRandomPath(t *testing.T) {
|
||||
|
||||
initSeedRand()
|
||||
secondPathLength := seedRandSource.Intn(20)
|
||||
secondPath := "/article/"
|
||||
for i := 0; i < secondPathLength+5; i++ {
|
||||
var secondPath strings.Builder
|
||||
secondPath.WriteString(articlePathPrefix)
|
||||
for idx := 0; idx < secondPathLength+5; idx++ {
|
||||
randomChar := seedRandSource.Intn(26)
|
||||
secondPath += string(rune('a' + randomChar))
|
||||
secondPath.WriteString(string(rune('a' + randomChar)))
|
||||
}
|
||||
|
||||
if path == secondPath {
|
||||
if path == secondPath.String() {
|
||||
t.Error("Generated paths should be different")
|
||||
}
|
||||
}
|
||||
@@ -333,7 +337,7 @@ func TestSeedCommandIdempotency(t *testing.T) {
|
||||
|
||||
seedUserCount := 0
|
||||
for _, user := range users {
|
||||
if user.Username == "seed_admin" {
|
||||
if user.Username == seedUsername {
|
||||
seedUserCount++
|
||||
}
|
||||
}
|
||||
@@ -369,10 +373,10 @@ func TestSeedCommandIdempotency(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("database remains consistent after multiple runs", func(t *testing.T) {
|
||||
for i := range 2 {
|
||||
for idx := range 2 {
|
||||
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--users", "0", "--posts", "1"})
|
||||
if err != nil {
|
||||
t.Fatalf("Seed run %d failed: %v", i+1, err)
|
||||
t.Fatalf("Seed run %d failed: %v", idx+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,9 +421,9 @@ func TestSeedCommandIdempotency(t *testing.T) {
|
||||
}
|
||||
|
||||
func findSeedUser(users []database.User) *database.User {
|
||||
for i := range users {
|
||||
if users[i].Username == "seed_admin" {
|
||||
return &users[i]
|
||||
for idx := range users {
|
||||
if users[idx].Username == seedUsername {
|
||||
return &users[idx]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -519,14 +523,14 @@ func TestEnsureSeedUser(t *testing.T) {
|
||||
}
|
||||
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
passwordHash := "test_password_hash"
|
||||
passwordHash := testPasswordHash
|
||||
|
||||
firstUser, err := ensureSeedUser(userRepo, passwordHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create seed user: %v", err)
|
||||
}
|
||||
|
||||
if firstUser.Username != "seed_admin" || firstUser.Email != "seed_admin@goyco.local" || firstUser.Password != passwordHash || !firstUser.EmailVerified {
|
||||
if firstUser.Username != seedUsername || firstUser.Email != seedEmail || firstUser.Password != passwordHash || !firstUser.EmailVerified {
|
||||
t.Errorf("Invalid seed user: username=%s, email=%s, password matches=%v, emailVerified=%v",
|
||||
firstUser.Username, firstUser.Email, firstUser.Password == passwordHash, firstUser.EmailVerified)
|
||||
}
|
||||
@@ -540,9 +544,9 @@ func TestEnsureSeedUser(t *testing.T) {
|
||||
t.Errorf("Expected same user to be reused (ID %d), got different user (ID %d)", firstUser.ID, secondUser.ID)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for idx := range 3 {
|
||||
if _, err := ensureSeedUser(userRepo, passwordHash); err != nil {
|
||||
t.Fatalf("Call %d failed: %v", i+1, err)
|
||||
t.Fatalf("Call %d failed: %v", idx+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +557,7 @@ func TestEnsureSeedUser(t *testing.T) {
|
||||
|
||||
seedUserCount := 0
|
||||
for _, user := range users {
|
||||
if user.Username == "seed_admin" {
|
||||
if user.Username == seedUsername {
|
||||
seedUserCount++
|
||||
}
|
||||
}
|
||||
@@ -565,7 +569,7 @@ func TestEnsureSeedUser(t *testing.T) {
|
||||
|
||||
func TestEnsureSeedUser_HandlesDatabaseErrors(t *testing.T) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
passwordHash := "test_password_hash"
|
||||
passwordHash := testPasswordHash
|
||||
|
||||
dbError := fmt.Errorf("database connection failed")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @title Goyco API
|
||||
// @version 0.1.0
|
||||
// @version 0.1.1
|
||||
// @description Goyco is a Y Combinator-style news aggregation platform API.
|
||||
// @contact.name Goyco Team
|
||||
// @contact.email sandro@cazzaniga.fr
|
||||
@@ -55,7 +55,7 @@ func run(args []string) error {
|
||||
|
||||
docs.SwaggerInfo.Title = fmt.Sprintf("%s API", cfg.App.Title)
|
||||
docs.SwaggerInfo.Description = "Y Combinator-style news board API."
|
||||
docs.SwaggerInfo.Version = version.Version
|
||||
docs.SwaggerInfo.Version = version.GetVersion()
|
||||
docs.SwaggerInfo.BasePath = "/api"
|
||||
docs.SwaggerInfo.Host = fmt.Sprintf("%s:%s", cfg.Server.Host, cfg.Server.Port)
|
||||
docs.SwaggerInfo.Schemes = []string{"http"}
|
||||
|
||||
@@ -2129,7 +2129,7 @@ const docTemplate = `{
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "0.1.0",
|
||||
Version: "0.1.1",
|
||||
Host: "localhost:8080",
|
||||
BasePath: "/api",
|
||||
Schemes: []string{"http"},
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"name": "GPLv3",
|
||||
"url": "https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
},
|
||||
"version": "0.1.0"
|
||||
"version": "0.1.1"
|
||||
},
|
||||
"host": "localhost:8080",
|
||||
"basePath": "/api",
|
||||
|
||||
@@ -221,7 +221,7 @@ info:
|
||||
name: GPLv3
|
||||
url: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
title: Goyco API
|
||||
version: 0.1.0
|
||||
version: 0.1.1
|
||||
paths:
|
||||
/api:
|
||||
get:
|
||||
|
||||
@@ -75,7 +75,7 @@ func (h *APIHandler) GetAPIInfo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
apiInfo := map[string]any{
|
||||
"name": fmt.Sprintf("%s API", h.config.App.Title),
|
||||
"version": version.Version,
|
||||
"version": version.GetVersion(),
|
||||
"description": "Y Combinator-style news board API",
|
||||
"endpoints": map[string]any{
|
||||
"authentication": map[string]any{
|
||||
@@ -145,7 +145,7 @@ func (h *APIHandler) GetHealth(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if h.healthChecker != nil {
|
||||
health := h.healthChecker.CheckHealth()
|
||||
health["version"] = version.Version
|
||||
health["version"] = version.GetVersion()
|
||||
SendSuccessResponse(w, "Health check successful", health)
|
||||
return
|
||||
}
|
||||
@@ -155,7 +155,7 @@ func (h *APIHandler) GetHealth(w http.ResponseWriter, r *http.Request) {
|
||||
health := map[string]any{
|
||||
"status": "healthy",
|
||||
"timestamp": currentTimestamp,
|
||||
"version": version.Version,
|
||||
"version": version.GetVersion(),
|
||||
"services": map[string]any{
|
||||
"database": "connected",
|
||||
"api": "running",
|
||||
@@ -230,7 +230,7 @@ func (h *APIHandler) GetMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
"system": map[string]any{
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
||||
"version": version.Version,
|
||||
"version": version.GetVersion(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
package version
|
||||
|
||||
const Version = "0.1.0"
|
||||
const version = "0.1.1"
|
||||
|
||||
func GetVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
@@ -8,39 +8,39 @@ import (
|
||||
func TestVersionSemver(t *testing.T) {
|
||||
semverRegex := regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
|
||||
|
||||
if !semverRegex.MatchString(Version) {
|
||||
t.Errorf("Version %q does not follow semantic versioning format (MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD])", Version)
|
||||
if !semverRegex.MatchString(GetVersion()) {
|
||||
t.Errorf("Version %q does not follow semantic versioning format (MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD])", GetVersion())
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionSemverFlexible(t *testing.T) {
|
||||
flexibleSemverRegex := regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
|
||||
|
||||
if !flexibleSemverRegex.MatchString(Version) {
|
||||
t.Errorf("Version %q does not follow semantic versioning format (MAJOR.MINOR[.PATCH][-PRERELEASE][+BUILD])", Version)
|
||||
if !flexibleSemverRegex.MatchString(GetVersion()) {
|
||||
t.Errorf("Version %q does not follow semantic versioning format (MAJOR.MINOR[.PATCH][-PRERELEASE][+BUILD])", GetVersion())
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionNotEmpty(t *testing.T) {
|
||||
if Version == "" {
|
||||
if GetVersion() == "" {
|
||||
t.Error("Version should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionFormat(t *testing.T) {
|
||||
if !regexp.MustCompile(`\d+\.\d+`).MatchString(Version) {
|
||||
t.Errorf("Version %q should contain at least MAJOR.MINOR format", Version)
|
||||
if !regexp.MustCompile(`\d+\.\d+`).MatchString(GetVersion()) {
|
||||
t.Errorf("Version %q should contain at least MAJOR.MINOR format", GetVersion())
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionStartsWithNumber(t *testing.T) {
|
||||
if !regexp.MustCompile(`^\d+`).MatchString(Version) {
|
||||
t.Errorf("Version %q should start with a number", Version)
|
||||
if !regexp.MustCompile(`^\d+`).MatchString(GetVersion()) {
|
||||
t.Errorf("Version %q should start with a number", GetVersion())
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionNoLeadingZeros(t *testing.T) {
|
||||
parts := regexp.MustCompile(`^(\d+)\.(\d+)`).FindStringSubmatch(Version)
|
||||
parts := regexp.MustCompile(`^(\d+)\.(\d+)`).FindStringSubmatch(GetVersion())
|
||||
if len(parts) >= 3 {
|
||||
major := parts[1]
|
||||
minor := parts[2]
|
||||
|
||||
Reference in New Issue
Block a user