1package storage23import (4 "bytes"5 "context"6 "fmt"7 "io"8 "slices"9 "testing"10 "time"1112 "git.lin.moe/go/mlisting/tools/testdata"13 gomsg "github.com/emersion/go-message"14 "github.com/emersion/go-message/mail"15)1617func StorageTestListCRUD(t *testing.T, st Storage) {18 ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)19 defer cancel()2021 listname := func(i uint8) string {22 return fmt.Sprintf("TestList%d", i)23 }24 listaddr := func(i uint8) string {25 return fmt.Sprintf("testlist%d@base.lan", i)26 }27 listdesc := func(i uint8) string {28 return fmt.Sprintf("test description for list %d", i)29 }30 listperms := []uint8{31 PERM_BROWSE | PERM_REPLY | PERM_POST,32 PERM_BROWSE | PERM_REPLY,33 PERM_BROWSE,34 }35 var err error3637 init_lists, err := st.Lists(ctx)38 if err != nil {39 t.Fatal(err)40 }41 lists := make([]List, len(listperms))4243 for i, p := range listperms {44 lists[i], err = st.NewList(ctx, listname(p), listaddr(p), listdesc(p), p)45 if err != nil {46 t.Fatal(err)47 }48 }49 _, err = st.NewList(ctx, "", listaddr(listperms[0]), "", listperms[0])50 if err == nil {51 t.Fatal("lists in storage should not allow same address, but it has")52 }5354 _lists, err := st.Lists(ctx)55 if err != nil {56 t.Fatal(err)57 } else if len(_lists) != len(init_lists)+len(listperms) {58 t.Fatalf("got wrong list number, except %d, get %d", len(init_lists)+len(listperms), len(_lists))59 }60 for _, l := range lists {61 found_flag := false62 for _, _l := range _lists {63 if _l.Name() == l.Name() && _l.Address() == _l.Address() {64 found_flag = true65 break66 }67 }68 if found_flag != true {69 t.Fatal("not found added list in storage.Lists result")70 }71 }7273 for i, l := range lists {74 _l, err := st.GetList(ctx, l.Address())75 if err != nil {76 t.Fatal(err)77 }78 if _l.Name() != l.Name() || _l.Name() != listname(listperms[i]) {79 t.Fatal("get wrong list name from storage")80 }81 if _l.Address() != l.Address() || _l.Address() != listaddr(listperms[i]) {82 t.Fatal("get wrong list address from storage")83 }84 if _l.Description() != l.Description() || _l.Description() != listdesc(listperms[i]) {85 t.Fatal("get wrong list description from storage")86 }87 if _l.DefaultPerm() != l.DefaultPerm() || _l.DefaultPerm() != listperms[i] {88 t.Fatal("get wrong list default permission from storage")89 }90 }9192 for _, l := range lists {93 if err := st.DeleteList(ctx, l.Address()); err != nil {94 t.Fatal(err)95 }96 }9798 _lists, err = st.Lists(ctx)99 if err != nil {100 t.Fatal(err)101 } else if len(_lists) != len(init_lists) {102 t.Fatal("clean lists failed")103 }104105}106107func StorageTestMemberCRUD(t *testing.T, st Storage) {108 const MEMBER_COUNT = 10109 var (110 str_members = make([]string, MEMBER_COUNT)111 members = make([]AddressInfo, MEMBER_COUNT)112 )113 for i := 0; i < MEMBER_COUNT; i += 1 {114 str_members[i] = fmt.Sprintf("Member%d <mem%d@base.lan>", i, i)115 }116117 ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)118 defer cancel()119120 lists, err := st.Lists(ctx)121 if err != nil {122 t.Fatal(err)123 } else if len(lists) == 0 {124 t.Fatal("not found lists")125 }126127 l1 := lists[0]128 init_mems, err := l1.Members(ctx)129 if err != nil {130 t.Fatal(err)131 }132133 for i, m := range str_members {134 mem, err := l1.NewMember(ctx, m)135 if err != nil {136 t.Fatal(err)137 }138 members[i] = mem139 }140 _, err = l1.NewMember(ctx, members[0].Address())141 if err == nil {142 t.Fatal("members in same list should not have same address, but it has")143 }144145 if mems, err := l1.Members(ctx); err != nil {146 t.Fatal(err)147 } else if len(mems) != len(init_mems)+MEMBER_COUNT {148 t.Fatal("account wrong member number")149 }150151 if l1.DefaultPerm() != members[0].Perm() {152 t.Fatal("account wrong member default permmision")153 }154155 for i, perm := range []uint8{156 PERM_BROWSE | PERM_REPLY | PERM_POST,157 PERM_BROWSE | PERM_REPLY,158 PERM_BROWSE,159 } {160 mem, err := l1.UpdateMember(ctx, members[i].Address(), perm)161 if err != nil {162 t.Fatal(err)163 }164 members[i], err = l1.GetMember(ctx, members[i].Address())165 if err != nil {166 t.Fatal(err)167 }168 if mem.Perm() != perm || members[i].Perm() != perm {169 t.Fatal("update list member permission failed")170 }171 }172173 for i, mem := range members {174 if mem.Name() != fmt.Sprintf("Member%d", i) {175 t.Fatalf("wrong member name, execpt %s, has %s", fmt.Sprintf("Member%d", i), mem.Name())176 }177 if mem.Address() != fmt.Sprintf("mem%d@base.lan", i) {178 t.Fatalf("wrong member address, execpt %s, has %s", fmt.Sprintf("mem%d@base.lan", i), mem.Address())179 }180 joined_lists, err := mem.JoinedLists(ctx)181 if err != nil {182 t.Fatal(err)183 }184 var joined_flag = false185 for _, jl := range joined_lists {186 if jl.Address() == l1.Address() {187 joined_flag = true188 break189 }190 }191 if joined_flag == false {192 t.Fatal("not found joined list in member")193 }194 }195196 for _, mem := range members {197 if err := l1.DelMember(ctx, mem.String()); err != nil {198 t.Fatal(err)199 }200 }201 if mems, err := l1.Members(ctx); err != nil {202 t.Fatal(err)203 } else if len(mems) != len(init_mems) {204 t.Fatal("clean members failed")205 }206}207208func StorageTestMessageUpdate(t *testing.T, st Storage) {209 ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)210 defer cancel()211212 l, err := st.NewList(ctx, "test", "test@base.lan", "list for test", PERM_BROWSE|PERM_REPLY)213 if err != nil {214 t.Fatal(err)215 }216217 // header backend data struct is dict, which is un-orderd218 msgHeaderEqual := func(h1 gomsg.Header, h2 gomsg.Header) bool {219 if h1.Len() != h2.Len() {220 return false221 }222 f1 := h1.Fields()223 for f1.Next() {224 if f1.Value() != h2.Get(f1.Key()) {225 return false226 }227 }228 return true229 }230231 // msgEqual has side effects: read entity.Body232 msgEqual := func(msg1 *gomsg.Entity, msg2 *gomsg.Entity) bool {233 if !msgHeaderEqual(msg1.Header, msg2.Header) {234 return false235 }236 var (237 buf1, buf2 *bytes.Buffer238 )239 buf1 = bytes.NewBuffer(nil)240 io.Copy(buf1, msg1.Body)241242 buf2 = bytes.NewBuffer(nil)243 io.Copy(buf2, msg2.Body)244245 return slices.Equal(buf1.Bytes(), buf2.Bytes())246 }247248 for _, msg := range testdata.TestMessages {249 header := gomsg.HeaderFromMap(msg.Header)250251 st_msg, err := l.AddMessage(ctx, msg.Header, msg.Body)252253 if err != nil {254 t.Fatal(err)255 }256257 if !msgHeaderEqual(header, st_msg.Header().Header) {258 t.Fatal("messsage Headers from storage is not euqal to original headers")259 }260261 ori_entity, _ := gomsg.New(gomsg.HeaderFromMap(msg.Header), bytes.NewReader(msg.Body))262263 if !msgEqual(ori_entity, st_msg.Entity()) {264 t.Fatal("the raw message from storage is not euqal to the original message")265 }266 }267268 ori_msg := testdata.TestMessages[0]269270 _, err = l.AddMessage(ctx, ori_msg.Header, ori_msg.Body)271 if err == nil {272 t.Fatal("list should not add messages have same Message-ID header")273 }274275 header := mail.HeaderFromMap(ori_msg.Header)276 msgId, _ := header.MessageID()277 msg, err := l.Message(ctx, msgId)278 if err != nil {279 t.Fatal(err)280 }281 ori_entity, _ := gomsg.New(gomsg.HeaderFromMap(ori_msg.Header), bytes.NewReader(ori_msg.Body))282 if !msgEqual(ori_entity, msg.Entity()) {283 t.Fatalf("query message by id get wrong result, query id %s, get %s",284 ori_msg.Header.Get("Message-Id"), msg.Header().Get("Message-Id"))285 }286287 if err := st.DeleteList(ctx, l.Address()); err != nil {288 t.Fatal(err)289 }290}291292func StorageTestMessageQuery(t *testing.T, st Storage) {293 ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)294 defer cancel()295296 _l, err := st.Lists(ctx)297 if err != nil {298 t.Fatal(err)299 } else if len(_l) == 0 {300 t.Fatal("not found test lists")301 }302303 l := _l[0]304 msgs, _, err := l.Messages(ctx, true, "", 0, uint(len(testdata.TestMessages)))305 if err != nil {306 t.Fatal(err)307 }308 for _, msg := range msgs {309 if msg.Header().Has("In-Reply-To") {310 t.Log(msg.Header())311 t.Fatal("get wrong message with In-Reply-To header")312 }313 }314315 msgs, _, err = l.Messages(ctx, false, "", 0, uint(len(testdata.TestMessages)))316 if err != nil {317 t.Fatal(err)318 }319 got_sub_msg := false320 for _, msg := range msgs {321 if msg.Header().Has("In-Reply-To") {322 got_sub_msg = true323 }324 }325 if !got_sub_msg {326 t.Fatal("not found sub message")327 }328329 header := mail.HeaderFromMap(testdata.TestMessages[0].Header)330 msg_id, _ := (&header).MessageID()331 key, _ := header.Subject()332 msgs, total, err := l.Messages(ctx, false, key, 0, uint(len(testdata.TestMessages)))333 is_searched := false334 if total != 1 {335 t.Fatalf("get multiple messages in the search result by subject %s", key)336 }337 for _, msg := range msgs {338 if id, err := msg.Header().MessageID(); err != nil {339 continue340 } else if id == msg_id {341 is_searched = true342 }343 }344 if !is_searched {345 t.Fatalf("not found message [%s] by keyword [%s]", msg_id, key)346 }347}