Compare commits
No commits in common. "feature/sharing-todos" and "main" have entirely different histories.
feature/sh
...
main
@ -50,24 +50,12 @@ func CreateNewTodo() common.Todo {
|
||||
for i, l := range labelSlice {
|
||||
labelSlice[i] = strings.TrimSpace(l)
|
||||
}
|
||||
|
||||
sharedInput := common.AskUserString("Shared with (comma separated usernames):\n")
|
||||
sharedSlice := strings.Split(sharedInput, ",")
|
||||
var sharedWith []string
|
||||
for _, s := range sharedSlice {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" {
|
||||
sharedWith = append(sharedWith, s)
|
||||
}
|
||||
}
|
||||
|
||||
return common.Todo{
|
||||
Name: common.AskUserString("Name:\n"),
|
||||
Description: common.AskUserString("Description:\n"),
|
||||
Status: common.AskUserString(fmt.Sprintf("Status (%s, %s, %s, %s, %s, %s):\n",
|
||||
common.NotStarted, common.Done, common.WIP, common.Pending, common.Blocked, common.Failed)),
|
||||
Labels: labelSlice,
|
||||
SharedWith: sharedWith,
|
||||
Owner: cfg.Server.Credentials.Username,
|
||||
LastModified: time.Now().UTC(),
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ module gitea.kleinsense.nl/DariusKlein/kleinTodo/client/todo
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404115537-7cf6eabbdd1d
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260118191144-e0c04fb9d1e9
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/urfave/cli/v3 v3.8.0
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404115537-7cf6eabbdd1d h1:YpsaXckG7ryvEe7cTwRrGNOB43wLnMlK0vXqYMHdrXQ=
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404115537-7cf6eabbdd1d/go.mod h1:owENFzNmtoCmr7ZUjNbkO0i+ugwqKdXCVikfOOcOsWk=
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260118191144-e0c04fb9d1e9 h1:EGFIRDjHIEt0IEFYeN2NEn/NyVglN6vXB6IRjm1rN0I=
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260118191144-e0c04fb9d1e9/go.mod h1:bHquapurFm/eUTtrl9mGLEdAYc5cOeueHFvqjommp44=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
|
||||
@ -141,21 +141,6 @@ func handleUpdate(scanner *bufio.Scanner, todos []common.Todo, store *common.Bol
|
||||
itemToUpdate.Status = newStatus
|
||||
}
|
||||
|
||||
fmt.Printf("New shared with (comma separated) [%s]: ", strings.Join(itemToUpdate.SharedWith, ", "))
|
||||
scanner.Scan()
|
||||
newSharedWith := strings.TrimSpace(scanner.Text())
|
||||
if newSharedWith != "" {
|
||||
sharedSlice := strings.Split(newSharedWith, ",")
|
||||
var sharedWith []string
|
||||
for _, s := range sharedSlice {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" {
|
||||
sharedWith = append(sharedWith, s)
|
||||
}
|
||||
}
|
||||
itemToUpdate.SharedWith = sharedWith
|
||||
}
|
||||
|
||||
err = itemToUpdate.Store(store, cfg.Server.Credentials.Username)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
|
||||
@ -7,8 +7,6 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"slices"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@ -221,53 +219,35 @@ func (s *BoltStore) ExistsByKey(bucket, key string) (bool, error) {
|
||||
}
|
||||
|
||||
func (s *BoltStore) GetTodoMap(user string) map[string]Todo {
|
||||
storedTodoJsons := s.GetAllFromBucket(user)
|
||||
|
||||
serverTodos := make(map[string]Todo)
|
||||
|
||||
s.DB.View(func(tx *bolt.Tx) error {
|
||||
return tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
||||
bucketName := string(name)
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
var todo Todo
|
||||
if json.Unmarshal(v, &todo) == nil {
|
||||
if bucketName == user || slices.Contains(todo.SharedWith, user) {
|
||||
serverTodos[string(k)] = todo
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
for key, val := range storedTodoJsons {
|
||||
var todo Todo
|
||||
if json.Unmarshal([]byte(val), &todo) == nil {
|
||||
serverTodos[key] = todo
|
||||
}
|
||||
}
|
||||
return serverTodos
|
||||
}
|
||||
|
||||
func (s *BoltStore) GetTodoList(user string, includeDeleted bool) []Todo {
|
||||
storedTodoJsons := s.GetAllFromBucket(user)
|
||||
|
||||
var storedTodos []Todo
|
||||
|
||||
s.DB.View(func(tx *bolt.Tx) error {
|
||||
return tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
||||
bucketName := string(name)
|
||||
c := b.Cursor()
|
||||
for _, v := c.First(); v != nil; _, v = c.Next() {
|
||||
var todo Todo
|
||||
if json.Unmarshal(v, &todo) == nil {
|
||||
if bucketName == user || slices.Contains(todo.SharedWith, user) {
|
||||
var include = false
|
||||
if includeDeleted {
|
||||
include = true
|
||||
} else {
|
||||
include = !todo.Deleted
|
||||
}
|
||||
if include {
|
||||
storedTodos = append(storedTodos, todo)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, val := range storedTodoJsons {
|
||||
var todo Todo
|
||||
if json.Unmarshal([]byte(val), &todo) == nil {
|
||||
var include = false
|
||||
if includeDeleted {
|
||||
include = true
|
||||
} else {
|
||||
include = !todo.Deleted
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
if include {
|
||||
storedTodos = append(storedTodos, todo)
|
||||
}
|
||||
}
|
||||
}
|
||||
return storedTodos
|
||||
}
|
||||
|
||||
@ -3,8 +3,9 @@ module gitea.kleinsense.nl/DariusKlein/kleinTodo/common
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
go.etcd.io/bbolt v1.4.3
|
||||
golang.org/x/crypto v0.49.0
|
||||
@ -17,6 +18,7 @@ require (
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
@ -12,12 +14,14 @@ github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSg
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
||||
@ -38,13 +42,15 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -13,10 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func (todo *Todo) Store(store *BoltStore, user string) error {
|
||||
isOwner := todo.Owner == user
|
||||
isShared := slices.Contains(todo.SharedWith, user)
|
||||
|
||||
if !isOwner && !isShared {
|
||||
if todo.Owner != user {
|
||||
return fmt.Errorf("unauthorized user")
|
||||
}
|
||||
if todo.Id == "" {
|
||||
@ -27,7 +23,7 @@ func (todo *Todo) Store(store *BoltStore, user string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return store.SaveValueToBucket(todo.Owner, todo.Id, string(todoJson))
|
||||
return store.SaveValueToBucket(user, todo.Id, string(todoJson))
|
||||
}
|
||||
|
||||
func (todoRequest StoreTodoRequest) Store(store *BoltStore, user string) error {
|
||||
@ -37,7 +33,6 @@ func (todoRequest StoreTodoRequest) Store(store *BoltStore, user string) error {
|
||||
Description: todoRequest.Description,
|
||||
Status: todoRequest.Status,
|
||||
Owner: user,
|
||||
SharedWith: todoRequest.SharedWith,
|
||||
LastModified: time.Now().UTC(),
|
||||
}
|
||||
todoJson, err := json.Marshal(todo)
|
||||
@ -108,9 +103,6 @@ func (todo Todo) PrintIndexed(index int) {
|
||||
uuidStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
|
||||
lastMod := todo.LastModified.Format("2006-01-02 15:04")
|
||||
metaLine := fmt.Sprintf(" Last Modified: %s • ID: %s", lastMod, todo.Id)
|
||||
if len(todo.SharedWith) > 0 {
|
||||
metaLine = fmt.Sprintf("%s • Shared with: %s", metaLine, strings.Join(todo.SharedWith, ", "))
|
||||
}
|
||||
fmt.Println(uuidStyle.Render(metaLine))
|
||||
|
||||
fmt.Printf(" %s----------------------------%s\n", ColorGray, ColorReset)
|
||||
@ -122,7 +114,6 @@ func (t Todo) IsEqual(other Todo) bool {
|
||||
t.Description == other.Description &&
|
||||
t.Status == other.Status &&
|
||||
t.Owner == other.Owner &&
|
||||
slices.Equal(t.SharedWith, other.SharedWith) &&
|
||||
t.Deleted == other.Deleted
|
||||
}
|
||||
|
||||
@ -131,6 +122,5 @@ func (t Todo) IsEqualIgnoringStatus(other Todo) bool {
|
||||
t.Name == other.Name &&
|
||||
t.Description == other.Description &&
|
||||
t.Owner == other.Owner &&
|
||||
slices.Equal(t.SharedWith, other.SharedWith) &&
|
||||
t.Deleted == other.Deleted
|
||||
}
|
||||
|
||||
@ -13,17 +13,15 @@ type Todo struct {
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Owner string `json:"owner"`
|
||||
SharedWith []string `json:"shared_with,omitzero"`
|
||||
Labels []string `json:"labels"`
|
||||
LastModified time.Time `json:"last_modified"`
|
||||
Deleted bool `json:"deleted"`
|
||||
}
|
||||
|
||||
type StoreTodoRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
SharedWith []string `json:"shared_with,omitzero"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type TodoList struct {
|
||||
|
||||
@ -2,7 +2,7 @@ module gitea.kleinsense.nl/DariusKlein/kleinTodo/server
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404115537-7cf6eabbdd1d
|
||||
require gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260118191144-e0c04fb9d1e9
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404115537-7cf6eabbdd1d h1:YpsaXckG7ryvEe7cTwRrGNOB43wLnMlK0vXqYMHdrXQ=
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404115537-7cf6eabbdd1d/go.mod h1:owENFzNmtoCmr7ZUjNbkO0i+ugwqKdXCVikfOOcOsWk=
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260118191144-e0c04fb9d1e9 h1:EGFIRDjHIEt0IEFYeN2NEn/NyVglN6vXB6IRjm1rN0I=
|
||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260118191144-e0c04fb9d1e9/go.mod h1:bHquapurFm/eUTtrl9mGLEdAYc5cOeueHFvqjommp44=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
|
||||
@ -1,134 +0,0 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/server/handler"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSharedTodoSync(t *testing.T) {
|
||||
// 1. Setup Environment
|
||||
tempDir, err := os.MkdirTemp("", "todo-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
os.Setenv("XDG_CONFIG_HOME", tempDir)
|
||||
os.Setenv("JWT_SECRET", "test-secret-123")
|
||||
|
||||
// 2. Initialize Server
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /register", handler.RegisterHandler)
|
||||
mux.HandleFunc("POST /login", handler.LoginHandler)
|
||||
mux.HandleFunc("POST /sync", handler.SyncHandler)
|
||||
mux.HandleFunc("POST /store", handler.StoreHandler)
|
||||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
client := ts.Client()
|
||||
|
||||
// 3. Register two users
|
||||
users := []struct {
|
||||
username, password string
|
||||
token string
|
||||
}{
|
||||
{"owner", "pass1", ""},
|
||||
{"shared", "pass2", ""},
|
||||
}
|
||||
|
||||
for i, u := range users {
|
||||
regPayload, _ := json.Marshal(common.Credentials{Username: u.username, Password: u.password})
|
||||
resp, err := client.Post(ts.URL+"/register", "application/json", bytes.NewBuffer(regPayload))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
resp, err = client.Post(ts.URL+"/login", "application/json", bytes.NewBuffer(regPayload))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
users[i].token = resp.Header.Get(common.AuthHeader)
|
||||
}
|
||||
|
||||
// 4. Owner creates a shared todo
|
||||
sharedTodo := common.Todo{
|
||||
Id: uuid.New().String(),
|
||||
Name: "Shared Task",
|
||||
Description: "This is shared",
|
||||
Status: common.NotStarted,
|
||||
Owner: "owner",
|
||||
SharedWith: []string{"shared"},
|
||||
LastModified: time.Now().UTC(),
|
||||
}
|
||||
|
||||
syncReq := common.TodoList{Todos: []common.Todo{sharedTodo}}
|
||||
syncPayload, _ := json.Marshal(syncReq)
|
||||
|
||||
req, _ := http.NewRequest("POST", ts.URL+"/sync", bytes.NewBuffer(syncPayload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set(common.AuthHeader, "Bearer "+users[0].token)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
// 5. Shared user pulls todos
|
||||
emptySyncReq := common.TodoList{Todos: []common.Todo{}}
|
||||
emptySyncPayload, _ := json.Marshal(emptySyncReq)
|
||||
req, _ = http.NewRequest("POST", ts.URL+"/sync", bytes.NewBuffer(emptySyncPayload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set(common.AuthHeader, "Bearer "+users[1].token)
|
||||
|
||||
resp, err = client.Do(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var syncResp common.SyncResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&syncResp)
|
||||
require.NoError(t, err)
|
||||
resp.Body.Close()
|
||||
|
||||
// Shared user should see the todo
|
||||
assert.Len(t, syncResp.SyncedTodos, 1)
|
||||
assert.Equal(t, "Shared Task", syncResp.SyncedTodos[0].Name)
|
||||
assert.Equal(t, "owner", syncResp.SyncedTodos[0].Owner)
|
||||
assert.Contains(t, syncResp.SyncedTodos[0].SharedWith, "shared")
|
||||
|
||||
// 6. Shared user updates the todo status
|
||||
updatedTodo := syncResp.SyncedTodos[0]
|
||||
updatedTodo.Status = common.Done
|
||||
updatedTodo.LastModified = time.Now().UTC()
|
||||
|
||||
updateSyncReq := common.TodoList{Todos: []common.Todo{updatedTodo}}
|
||||
updateSyncPayload, _ := json.Marshal(updateSyncReq)
|
||||
req, _ = http.NewRequest("POST", ts.URL+"/sync", bytes.NewBuffer(updateSyncPayload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set(common.AuthHeader, "Bearer "+users[1].token)
|
||||
|
||||
resp, err = client.Do(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
// 7. Owner pulls todos and checks status
|
||||
req, _ = http.NewRequest("POST", ts.URL+"/sync", bytes.NewBuffer(emptySyncPayload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set(common.AuthHeader, "Bearer "+users[0].token)
|
||||
|
||||
resp, err = client.Do(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&syncResp)
|
||||
require.NoError(t, err)
|
||||
resp.Body.Close()
|
||||
|
||||
assert.Len(t, syncResp.SyncedTodos, 1)
|
||||
assert.Equal(t, common.Done, syncResp.SyncedTodos[0].Status, "Owner should see status update from shared user")
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user