Files
mesh-catalog/modules/mesh-mail/cmd/mesh-mail/pgstore_test.go
T
jochen 70c1110c53
mesh/merge-gate out of date: judged against trunk e22326240833, the trunk is now 482232e8c51c; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk e22326240833, the trunk is now 482232e8c51c; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
Add mesh-mail: watch the operator's own mailbox read-only and say each mail on the bus
ADR 0359 decided one module holds the operator's mail accounts. This is its
first stage: an imap adapter that keeps IDLE on the inbox and polls the other
folders read-only, indexes each message in its own database, keeps each
attachment once by its SHA-256 in its bucket, and says every added, changed or
removed message as a metadata-only event. Bodies and attachments are served
only by its tools, to callers the trusted setting grants names. Its health
waits for the account's own secret until it is given at the desk (issue 710).
2026-10-14 20:14:42 +02:00

100 lines
3.4 KiB
Go

package main
import (
"context"
"reflect"
"strings"
"testing"
)
// The schema is numbered migrations and a consolidated schema for a fresh database (the mesh's rule: both). A
// database given the consolidated schema and one given every migration in order hold the same tables and columns.
func TestTheConsolidatedSchemaIsTheMigrationsApplied(t *testing.T) {
ctx := context.Background()
fresh := pgStore(t)
stepped, err := OpenPgStore(ctx, freshDatabase(t))
if err != nil {
t.Fatal(err)
}
defer stepped.Close()
migrations, err := Migrations()
if err != nil {
t.Fatal(err)
}
for _, m := range migrations {
if _, err := stepped.pool.Exec(ctx, m.SQL); err != nil {
t.Fatalf("migration %04d: %v", m.Number, err)
}
}
a, b := shape(t, fresh), shape(t, stepped)
if !reflect.DeepEqual(a, b) || len(a) < 25 {
t.Fatalf("the consolidated schema and the migrations differ:\nconsolidated %v\nmigrations %v", a, b)
}
if !strings.Contains(consolidated, "through migration 0001") || len(migrations) != 1 {
t.Fatal("the consolidated schema does not say how far it is consolidated")
}
// Prepared twice, it changes nothing; and it is ready.
if said, err := fresh.Prepare(ctx); err != nil || !strings.Contains(said[0], "as this version needs") {
t.Fatalf("%v %v", said, err)
}
if err := fresh.Ready(ctx); err != nil {
t.Fatal(err)
}
if err := stepped.Ready(ctx); err == nil {
t.Fatal("a database whose migrations are not recorded is ready")
}
}
func shape(t *testing.T, s *PgStore) []string {
t.Helper()
rows, err := s.pool.Query(context.Background(), `select table_name || '.' || column_name || ' ' || data_type
from information_schema.columns where table_schema = 'public' and table_name <> 'migration' order by 1`)
if err != nil {
t.Fatal(err)
}
defer rows.Close()
var out []string
for rows.Next() {
var c string
_ = rows.Scan(&c)
out = append(out, c)
}
return out
}
// The search finds by every word, across the accounts asked only, newest first, and answers no body.
func TestTheIndexSearchesByWordAndAccount(t *testing.T) {
eachStore(t, func(t *testing.T, st Store) {
ctx := context.Background()
for i, subj := range []string{"the invoice of october", "lunch", "another invoice"} {
m := Message{Account: "mesh", Folder: "INBOX", UID: uint32(i + 1), Subject: subj, Date: judgedAt.AddDate(0, 0, i),
From: []Person{{Address: "bob@example.org"}}, To: []Person{}, Flags: []string{}, Attachments: []AttachmentRef{}, Body: "text " + subj}
if err := st.Add(ctx, m, nil); err != nil {
t.Fatal(err)
}
}
other := Message{Account: "gmail", Folder: "INBOX", UID: 1, Subject: "invoice elsewhere", Date: judgedAt, From: []Person{}, To: []Person{},
Flags: []string{}, Attachments: []AttachmentRef{}}
if err := st.Add(ctx, other, nil); err != nil {
t.Fatal(err)
}
hits, err := st.Search(ctx, Query{Text: "invoice", Accounts: []string{"mesh"}, Limit: 10})
if err != nil {
t.Fatal(err)
}
if len(hits) != 2 || hits[0].Subject != "another invoice" || hits[0].Body != "" {
t.Fatalf("%+v", hits)
}
if hits, _ := st.Search(ctx, Query{Text: "invoice october", Accounts: []string{"mesh"}}); len(hits) != 1 {
t.Fatalf("%+v", hits)
}
if hits, _ := st.Search(ctx, Query{Text: "bob", Accounts: []string{"mesh"}}); len(hits) != 3 {
t.Fatalf("by sender: %+v", hits)
}
got, ok, err := st.Get(ctx, "mesh", "INBOX", 2)
if err != nil || !ok || got.Body != "text lunch" {
t.Fatalf("%+v %v %v", got, ok, err)
}
})
}