doc_test.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. package sharedspace
  2. import (
  3. "path/filepath"
  4. "strings"
  5. "testing"
  6. )
  7. func newDocTestSpace(t *testing.T) *Space {
  8. t.Helper()
  9. m := NewManager(filepath.Join(t.TempDir(), "spaces"), 0)
  10. return m.CreateSpace("alice", "Doc test")
  11. }
  12. func TestDocLifecycle(t *testing.T) {
  13. space := newDocTestSpace(t)
  14. doc, err := space.CreateDoc("alice", "Meeting notes")
  15. if err != nil {
  16. t.Fatalf("CreateDoc() error = %v", err)
  17. }
  18. if doc.Revision != 1 || doc.Name != "Meeting notes" || doc.Creator != "alice" {
  19. t.Errorf("new doc snapshot = %+v", doc)
  20. }
  21. //Default name
  22. unnamed, _ := space.CreateDoc("alice", "")
  23. if unnamed.Name != "Untitled document" {
  24. t.Errorf("unnamed doc name = %q", unnamed.Name)
  25. }
  26. got, ok := space.GetDoc(doc.ID)
  27. if !ok || got.ID != doc.ID {
  28. t.Fatalf("GetDoc() did not return the document")
  29. }
  30. if _, ok := space.GetDoc("nonexistent"); ok {
  31. t.Errorf("GetDoc(unknown) returned ok")
  32. }
  33. list := space.ListDocs()
  34. if len(list) != 2 {
  35. t.Fatalf("ListDocs() = %d docs, want 2", len(list))
  36. }
  37. for _, snapshot := range list {
  38. if snapshot.Content != "" {
  39. t.Errorf("ListDocs() leaked content")
  40. }
  41. }
  42. if space.DocCount() != 2 {
  43. t.Errorf("DocCount() = %d, want 2", space.DocCount())
  44. }
  45. //Deletion permissions: stranger no, creator yes
  46. if err := space.DeleteDoc("mallory", doc.ID); err != ErrPermissionDenied {
  47. t.Errorf("stranger DeleteDoc error = %v, want ErrPermissionDenied", err)
  48. }
  49. if err := space.DeleteDoc("alice", doc.ID); err != nil {
  50. t.Errorf("creator DeleteDoc error = %v", err)
  51. }
  52. if err := space.DeleteDoc("alice", doc.ID); err != ErrDocNotFound {
  53. t.Errorf("double DeleteDoc error = %v, want ErrDocNotFound", err)
  54. }
  55. }
  56. func TestDocUpdateCAS(t *testing.T) {
  57. space := newDocTestSpace(t)
  58. doc, _ := space.CreateDoc("alice", "notes")
  59. //Sequential updates advance the revision
  60. first, err := space.UpdateDoc("alice", doc.ID, 1, "hello world")
  61. if err != nil {
  62. t.Fatalf("first UpdateDoc error = %v", err)
  63. }
  64. if first.Revision != 2 || first.Content != "hello world" {
  65. t.Errorf("first update snapshot = %+v", first)
  66. }
  67. second, err := space.UpdateDoc("bob", doc.ID, 2, "hello brave world")
  68. if err != nil {
  69. t.Fatalf("second UpdateDoc error = %v", err)
  70. }
  71. if second.Revision != 3 || second.UpdatedBy != "bob" {
  72. t.Errorf("second update snapshot = %+v", second)
  73. }
  74. //Stale base revision is rejected without content change
  75. if _, err := space.UpdateDoc("carol", doc.ID, 2, "clobber"); err != ErrRevisionConflict {
  76. t.Errorf("stale update error = %v, want ErrRevisionConflict", err)
  77. }
  78. if got, _ := space.GetDoc(doc.ID); got.Content != "hello brave world" {
  79. t.Errorf("conflict mutated content: %q", got.Content)
  80. }
  81. //Unknown doc and oversized content
  82. if _, err := space.UpdateDoc("alice", "nonexistent", 1, "x"); err != ErrDocNotFound {
  83. t.Errorf("unknown doc error = %v, want ErrDocNotFound", err)
  84. }
  85. if _, err := space.UpdateDoc("alice", doc.ID, 3, strings.Repeat("a", MaxDocLength+1)); err != ErrDocTooLarge {
  86. t.Errorf("oversized update error = %v, want ErrDocTooLarge", err)
  87. }
  88. //History records the accepted revisions
  89. history, ok := space.DocHistory(doc.ID)
  90. if !ok || len(history) != 2 {
  91. t.Fatalf("DocHistory() = %d entries, want 2", len(history))
  92. }
  93. if history[0].Revision != 2 || history[1].Revision != 3 {
  94. t.Errorf("history revisions = %d, %d", history[0].Revision, history[1].Revision)
  95. }
  96. }
  97. func TestDocLimits(t *testing.T) {
  98. space := newDocTestSpace(t)
  99. for i := 0; i < DefaultMaxDocs; i++ {
  100. if _, err := space.CreateDoc("alice", "doc"); err != nil {
  101. t.Fatalf("CreateDoc #%d error = %v", i, err)
  102. }
  103. }
  104. if _, err := space.CreateDoc("alice", "one too many"); err != ErrDocLimitReached {
  105. t.Errorf("over-limit CreateDoc error = %v, want ErrDocLimitReached", err)
  106. }
  107. }
  108. func TestDocPermissions(t *testing.T) {
  109. m := NewManager(filepath.Join(t.TempDir(), "spaces"), 0)
  110. space, _ := m.CreateSpaceWithOptions("alice", "private docs", SpaceOptions{Access: AccessPrivate})
  111. if _, err := space.CreateDoc("stranger", "sneaky"); err != ErrPermissionDenied {
  112. t.Errorf("stranger CreateDoc error = %v, want ErrPermissionDenied", err)
  113. }
  114. doc, err := space.CreateDoc("alice", "insider")
  115. if err != nil {
  116. t.Fatalf("owner CreateDoc error = %v", err)
  117. }
  118. if _, err := space.UpdateDoc("stranger", doc.ID, 1, "hijack"); err != ErrPermissionDenied {
  119. t.Errorf("stranger UpdateDoc error = %v, want ErrPermissionDenied", err)
  120. }
  121. //Space admins may delete docs they did not create
  122. space.AddMember("alice", "adam", RoleAdmin)
  123. if err := space.DeleteDoc("adam", doc.ID); err != nil {
  124. t.Errorf("admin DeleteDoc error = %v", err)
  125. }
  126. }
  127. func TestDocUpdateEmitsPatchEvent(t *testing.T) {
  128. space := newDocTestSpace(t)
  129. doc, _ := space.CreateDoc("alice", "notes")
  130. var events []*SpaceEvent
  131. space.SubscribeEvents("test", func(event *SpaceEvent) {
  132. events = append(events, event)
  133. })
  134. space.UpdateDoc("alice", doc.ID, 1, "hello")
  135. if len(events) != 1 || events[0].Kind != EventDocUpdated {
  136. t.Fatalf("expected one doc-updated event, got %d", len(events))
  137. }
  138. if events[0].Patch == nil || events[0].Patch.Ins != "hello" {
  139. t.Errorf("event patch = %+v", events[0].Patch)
  140. }
  141. if events[0].Doc == nil || events[0].Doc.Revision != 2 {
  142. t.Errorf("event snapshot = %+v", events[0].Doc)
  143. }
  144. space.DeleteDoc("alice", doc.ID)
  145. if len(events) != 2 || events[1].Kind != EventDocDeleted {
  146. t.Errorf("expected doc-deleted event, got %+v", events)
  147. }
  148. }
  149. func TestComputeSplicePatch(t *testing.T) {
  150. tests := []struct {
  151. name string
  152. oldStr string
  153. newStr string
  154. wantPos int
  155. wantDel int
  156. wantIns string
  157. }{
  158. {"append", "hello", "hello world", 5, 0, " world"},
  159. {"prepend", "world", "hello world", 0, 0, "hello "},
  160. {"insert middle", "helo", "hello", 3, 0, "l"},
  161. {"delete middle", "hello", "helo", 3, 1, ""},
  162. {"replace middle", "hello brave world", "hello bold world", 7, 4, "old"},
  163. {"clear all", "abc", "", 0, 3, ""},
  164. {"from empty", "", "abc", 0, 0, "abc"},
  165. {"no change", "same", "same", 4, 0, ""},
  166. {"unicode aware", "café au lait", "cafés au lait", 4, 0, "s"},
  167. {"full rewrite", "abc", "xyz", 0, 3, "xyz"},
  168. }
  169. for _, tt := range tests {
  170. t.Run(tt.name, func(t *testing.T) {
  171. patch := computeSplicePatch(tt.oldStr, tt.newStr)
  172. if patch.Pos != tt.wantPos || patch.Del != tt.wantDel || patch.Ins != tt.wantIns {
  173. t.Errorf("patch = %+v, want {Pos:%d Del:%d Ins:%q}", patch, tt.wantPos, tt.wantDel, tt.wantIns)
  174. }
  175. //Applying the patch must reproduce newStr
  176. oldRunes := []rune(tt.oldStr)
  177. rebuilt := string(oldRunes[:patch.Pos]) + patch.Ins + string(oldRunes[patch.Pos+patch.Del:])
  178. if rebuilt != tt.newStr {
  179. t.Errorf("patch application = %q, want %q", rebuilt, tt.newStr)
  180. }
  181. })
  182. }
  183. }