eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "net/http"
5 "net/http/httptest"
6 "os"
7 "path/filepath"
8 "strconv"
9 "testing"
10 "time"
11
12 "lchat/internal/client"
13 "lchat/internal/e2ee"
14)
15
16// fakeHome serves a sealed vault blob the way home's GET /api/blob does —
17// bearer-gated, opaque bytes.
18func fakeHome(t *testing.T, token string, envelope []byte) *httptest.Server {
19 t.Helper()
20 mux := http.NewServeMux()
21 mux.HandleFunc("GET /api/blob", func(w http.ResponseWriter, r *http.Request) {
22 if r.Header.Get("Authorization") != "Bearer "+token {
23 http.Error(w, "unauthorized", http.StatusUnauthorized)
24 return
25 }
26 w.Header().Set("Content-Type", "application/octet-stream")
27 w.Write(envelope)
28 })
29 ts := httptest.NewServer(mux)
30 t.Cleanup(ts.Close)
31 return ts
32}
33
34func sealVault(t *testing.T, v *client.Vault) (envelope []byte, dekB64 string) {
35 t.Helper()
36 dek, err := e2ee.GenerateKey()
37 if err != nil {
38 t.Fatal(err)
39 }
40 envelope, err = client.SealHomeBlob(v, dek, map[string]string{"cred-1": "opaque"})
41 if err != nil {
42 t.Fatal(err)
43 }
44 return envelope, e2ee.B64uEncode(dek)
45}
46
47// The silent re-sync path: a saved home session pulls the vault and fills the
48// store — no ceremony, no browser.
49func TestSigninResyncFillsStore(t *testing.T) {
50 vault := &client.Vault{V: 1, Servers: []client.VaultServer{
51 {URL: "https://alpha.11ch.at", Name: "Alpha", Starred: true,
52 Credentials: &client.VaultCredentials{Token: "tokA", UserPub: "pubA", UserPriv: "privA", Name: "Paul"}},
53 {URL: "https://gone.11ch.at", Name: "Old", Archived: true,
54 Credentials: &client.VaultCredentials{Token: "tokB", UserPub: "pubB", UserPriv: "privB"}},
55 {URL: "https://bare.11ch.at", Name: "Bookmark"},
56 }}
57 envelope, dekB64 := sealVault(t, vault)
58 home := fakeHome(t, "hometok", envelope)
59
60 dir := t.TempDir()
61 st, err := openStore(dir)
62 if err != nil {
63 t.Fatal(err)
64 }
65 if err := st.saveHomeSession(&client.HomeSession{V: 1, Base: home.URL, Token: "hometok", CredID: "cred-1", DEK: dekB64}); err != nil {
66 t.Fatal(err)
67 }
68
69 runSignin(st, nil) // dies (exits) on failure; success writes spaces
70
71 sessions, err := st.spaces()
72 if err != nil {
73 t.Fatal(err)
74 }
75 if len(sessions) != 1 {
76 t.Fatalf("want 1 restorable space, got %d", len(sessions))
77 }
78 s := sessions[0]
79 if s.Base != "https://alpha.11ch.at" || s.Token != "tokA" || s.Name != "Paul" ||
80 s.SpaceName != "Alpha" || !s.Starred || !s.LabelPublished {
81 t.Fatalf("restored session: %+v", s)
82 }
83 if _, err := os.Stat(filepath.Join(dir, "spaces", "alpha.11ch.at.json")); err != nil {
84 t.Fatalf("session file: %v", err)
85 }
86}
87
88// The loopback listener: the browser's redirect lands the ?d= payload with the
89// terminal, the success page renders, and duplicate hits don't wedge it.
90func TestLoopbackCallback(t *testing.T) {
91 lb, err := startLoopback()
92 if err != nil {
93 t.Fatal(err)
94 }
95 defer lb.close()
96
97 base := "http://127.0.0.1:" + strconv.Itoa(lb.port)
98 if resp, err := http.Get(base + "/cb"); err != nil || resp.StatusCode != http.StatusBadRequest {
99 t.Fatalf("missing d: %v %v", err, resp.Status)
100 }
101 for i := 0; i < 2; i++ { // a reload of the success page must not block
102 resp, err := http.Get(base + "/cb?d=sealed-code")
103 if err != nil || resp.StatusCode != http.StatusOK {
104 t.Fatalf("callback hit %d: %v", i, err)
105 }
106 resp.Body.Close()
107 }
108 select {
109 case d := <-lb.got:
110 if d != "sealed-code" {
111 t.Fatalf("got %q", d)
112 }
113 case <-time.After(2 * time.Second):
114 t.Fatal("payload never delivered")
115 }
116}
117
118// Legacy migration: a single-space ~/.eleven/session.json from the pre-signin
119// CLI moves into spaces/ on first store open.
120func TestLegacySessionMigration(t *testing.T) {
121 dir := t.TempDir()
122 legacy := &client.Session{Base: "https://old.11ch.at", Token: "tok", Name: "Paul", UserPub: "p", UserPriv: "k"}
123 if err := legacy.Save(filepath.Join(dir, "session.json")); err != nil {
124 t.Fatal(err)
125 }
126 st, err := openStore(dir)
127 if err != nil {
128 t.Fatal(err)
129 }
130 sessions, err := st.spaces()
131 if err != nil {
132 t.Fatal(err)
133 }
134 if len(sessions) != 1 || sessions[0].Base != "https://old.11ch.at" {
135 t.Fatalf("migrated: %+v", sessions)
136 }
137 if _, err := os.Stat(filepath.Join(dir, "session.json")); !os.IsNotExist(err) {
138 t.Fatal("legacy file still present")
139 }
140}
141