add: story and some tests
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
package story
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v1 "git.tipsy.codes/charles/webstory/pkg/api/webstory/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func TestStoryTranslator_FromAPI(t *testing.T) {
|
||||
trans := NewStoryTranslator()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
apiStory *v1.Story
|
||||
expectErr bool
|
||||
errMsg string
|
||||
expectID int64
|
||||
checkFunc func(*DBStory) error
|
||||
}{
|
||||
{
|
||||
name: "nil story returns error",
|
||||
apiStory: nil,
|
||||
expectErr: true,
|
||||
errMsg: "api story cannot be nil",
|
||||
},
|
||||
{
|
||||
name: "minimal valid story creates translator correctly",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "abc123",
|
||||
Title: "Test Story",
|
||||
},
|
||||
expectErr: false,
|
||||
expectID: 0,
|
||||
checkFunc: func(s *DBStory) error {
|
||||
if s.StoryID != "abc123" {
|
||||
return fmt.Errorf("expected StoryID abc123, got %s", s.StoryID)
|
||||
}
|
||||
if s.Title != "Test Story" {
|
||||
return fmt.Errorf("expected Title Test Story, got %s", s.Title)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "story with all fields converts correctly",
|
||||
apiStory: &v1.Story{
|
||||
Name: "stories/my-story-1",
|
||||
StoryId: "my-story-1",
|
||||
Title: "My Story Title",
|
||||
Content: "This is the story content",
|
||||
Description: "A wonderful story",
|
||||
Labels: []string{"fiction", "adventure"},
|
||||
CreateTime: timestamppb.New(now),
|
||||
UpdateTime: timestamppb.New(now),
|
||||
Etag: "etag-123",
|
||||
},
|
||||
expectErr: false,
|
||||
expectID: 0,
|
||||
checkFunc: func(s *DBStory) error {
|
||||
if s.StoryID != "my-story-1" {
|
||||
return fmt.Errorf("expected StoryID my-story-1, got %s", s.StoryID)
|
||||
}
|
||||
if s.Title != "My Story Title" {
|
||||
return fmt.Errorf("expected Title My Story Title, got %s", s.Title)
|
||||
}
|
||||
if s.Content != "This is the story content" {
|
||||
return fmt.Errorf("expected Content, got %s", s.Content)
|
||||
}
|
||||
if s.Description != "A wonderful story" {
|
||||
return fmt.Errorf("expected Description, got %s", s.Description)
|
||||
}
|
||||
if len(s.Labels) != 2 || s.Labels[0] != "fiction" || s.Labels[1] != "adventure" {
|
||||
return fmt.Errorf("expected Labels [fiction adventure], got %v", s.Labels)
|
||||
}
|
||||
if s.Etag != "etag-123" {
|
||||
return fmt.Errorf("expected Etag etag-123, got %s", s.Etag)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil labels converted to empty slice",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "test1",
|
||||
Title: "Test",
|
||||
Labels: nil,
|
||||
},
|
||||
expectErr: false,
|
||||
expectID: 0,
|
||||
checkFunc: func(s *DBStory) error {
|
||||
if s.Labels == nil {
|
||||
return fmt.Errorf("expected non-nil Labels slice, got nil")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty labels converted to empty slice",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "test2",
|
||||
Title: "Test",
|
||||
Labels: []string{},
|
||||
},
|
||||
expectErr: false,
|
||||
expectID: 0,
|
||||
checkFunc: func(s *DBStory) error {
|
||||
if s.Labels == nil {
|
||||
return fmt.Errorf("expected non-nil Labels slice, got nil")
|
||||
}
|
||||
if len(s.Labels) != 0 {
|
||||
return fmt.Errorf("expected empty Labels slice, got %v", s.Labels)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing title returns error",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "test3",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "title is required",
|
||||
},
|
||||
{
|
||||
name: "invalid story_id short returns error",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "ab",
|
||||
Title: "Test",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "invalid story_id format",
|
||||
},
|
||||
{
|
||||
name: "invalid story_id uppercase returns error",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "ABC123",
|
||||
Title: "Test",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "invalid story_id format",
|
||||
},
|
||||
{
|
||||
name: "invalid story_id starts with number returns error",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "1abc",
|
||||
Title: "Test",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "invalid story_id format",
|
||||
},
|
||||
{
|
||||
name: "valid story_id with hyphens and numbers",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "my-awesome-story-123",
|
||||
Title: "Test",
|
||||
},
|
||||
expectErr: false,
|
||||
expectID: 0,
|
||||
checkFunc: func(s *DBStory) error {
|
||||
if s.StoryID != "my-awesome-story-123" {
|
||||
return fmt.Errorf("expected StoryID my-awesome-story-123, got %s", s.StoryID)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "story_id auto-generated from name if not provided",
|
||||
apiStory: &v1.Story{
|
||||
StoryId: "auto-gen",
|
||||
Title: "Test",
|
||||
Name: "stories/auto-gen",
|
||||
},
|
||||
expectErr: false,
|
||||
expectID: 0,
|
||||
checkFunc: func(s *DBStory) error {
|
||||
if s.StoryID != "auto-gen" {
|
||||
return fmt.Errorf("expected StoryID auto-gen, got %s", s.StoryID)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := trans.FromAPI(tt.apiStory)
|
||||
|
||||
if tt.expectErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got nil")
|
||||
}
|
||||
if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
|
||||
t.Errorf("expected error containing '%s' but got '%s'", tt.errMsg, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
t.Fatalf("expected non-nil result")
|
||||
}
|
||||
|
||||
if result.ID != tt.expectID {
|
||||
t.Errorf("expected ID %d but got %d", tt.expectID, result.ID)
|
||||
}
|
||||
|
||||
if tt.checkFunc != nil {
|
||||
if err := tt.checkFunc(result); err != nil {
|
||||
t.Errorf("check failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoryTranslator_ToAPI(t *testing.T) {
|
||||
trans := NewStoryTranslator()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dbStory *DBStory
|
||||
expectErr bool
|
||||
checkFunc func(*v1.Story) error
|
||||
}{
|
||||
{
|
||||
name: "nil dbStory returns nil",
|
||||
dbStory: nil,
|
||||
expectErr: false,
|
||||
checkFunc: func(s *v1.Story) error {
|
||||
if s != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "minimal story converts correctly",
|
||||
dbStory: &DBStory{
|
||||
ID: 1,
|
||||
StoryID: "test-story",
|
||||
Title: "Test Title",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
expectErr: false,
|
||||
checkFunc: func(s *v1.Story) error {
|
||||
if s.GetName() != "stories/test-story" {
|
||||
return fmt.Errorf("expected Name stories/test-story, got %s", s.GetName())
|
||||
}
|
||||
if s.GetStoryId() != "test-story" {
|
||||
return fmt.Errorf("expected StoryId test-story, got %s", s.GetStoryId())
|
||||
}
|
||||
if s.GetTitle() != "Test Title" {
|
||||
return fmt.Errorf("expected Title Test Title, got %s", s.GetTitle())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "story with all fields converts correctly",
|
||||
dbStory: &DBStory{
|
||||
ID: 1,
|
||||
StoryID: "full-story",
|
||||
Title: "Full Title",
|
||||
Content: "Full Content",
|
||||
Description: "Full Description",
|
||||
Labels: []string{"label1", "label2"},
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Etag: "test-etag",
|
||||
},
|
||||
expectErr: false,
|
||||
checkFunc: func(s *v1.Story) error {
|
||||
if s.GetName() != "stories/full-story" {
|
||||
return fmt.Errorf("expected Name stories/full-story, got %s", s.GetName())
|
||||
}
|
||||
if s.Etag != "test-etag" {
|
||||
return fmt.Errorf("expected Etag test-etag, got %s", s.Etag)
|
||||
}
|
||||
if len(s.GetLabels()) != 2 {
|
||||
return fmt.Errorf("expected 2 labels, got %d", len(s.GetLabels()))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := trans.ToAPI(tt.dbStory)
|
||||
|
||||
if tt.expectErr {
|
||||
if result != nil {
|
||||
t.Fatalf("expected nil result but got %v", result)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if tt.checkFunc != nil {
|
||||
if err := tt.checkFunc(result); err != nil {
|
||||
t.Errorf("check failed: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBStory_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
story *DBStory
|
||||
expectErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "empty story_id returns error",
|
||||
story: &DBStory{
|
||||
StoryID: "",
|
||||
Title: "Test",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "story_id is required",
|
||||
},
|
||||
{
|
||||
name: "invalid story_id format returns error",
|
||||
story: &DBStory{
|
||||
StoryID: "INVALID",
|
||||
Title: "Test",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "invalid story_id format",
|
||||
},
|
||||
{
|
||||
name: "empty title returns error",
|
||||
story: &DBStory{
|
||||
StoryID: "valid-story-1",
|
||||
Title: "",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "title is required",
|
||||
},
|
||||
{
|
||||
name: "valid story passes validation",
|
||||
story: &DBStory{
|
||||
StoryID: "valid-story-1",
|
||||
Title: "Valid Title",
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.story.Validate()
|
||||
|
||||
if tt.expectErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got nil")
|
||||
}
|
||||
if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
|
||||
t.Errorf("expected error containing '%s' but got '%s'", tt.errMsg, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user