// Package main tests the relay command's pure helpers: path classification, // the proxy content-type gate, static-file serving, the crawl relay database // and its event parser, the hex/integer formatters, the crawl log, and // crawlPublishBatch's dial-error branch. // // crawlPublishBatch was left uncalled because a call SIGSEGVs // nondeterministically under the harness. That was the defer-frame JumpPC // never being armed - an unchecked longjmp to address 0 - which the compiler // now fixes. It is driven again in TestCrawlPublishBatchDialError. package main import ( "os" "testing" ) // mxHex64 is 64 lowercase hex characters, the shape of an event id or blob hash. const mxHex64 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" // testing.T.Fatal does not stop the test in this harness, so each check only // records the failure and returns. func mxChkS(t *testing.T, name, got, want string) { if got != want { t.Fatalf("%s = %s, want %s", name, got, want) } } func mxChkI32(t *testing.T, name string, got, want int32) { if got != want { t.Fatalf("%s = %d, want %d", name, got, want) } } func mxChkI64(t *testing.T, name string, got, want int64) { if got != want { t.Fatalf("%s = %d, want %d", name, got, want) } } func mxChkBool(t *testing.T, name string, got, want bool) { if got != want { if want { t.Fatalf("%s = false, want true", name) } t.Fatalf("%s = true, want false", name) } } // mxFill returns n copies of c as a string. func mxFill(n int32, c byte) (s string) { b := []byte{:n} for i := range b { b[i] = c } return string(b) } // mxWrite is os.WriteFile with the test's failure mode. func mxWrite(t *testing.T, path, content string) { if werr := os.WriteFile(path, []byte(content), 0644); werr != nil { t.Fatalf("write %s: %s", path, werr.Error()) } } // mxRawEvent builds an EVENT envelope with zero-filled id/pubkey/sig and the // supplied tags JSON. Unmarshal only length-checks the hex fields, so a signed // event is not needed to drive the parser. func mxRawEvent(kind uint16, tagsJSON string) (out []byte) { raw := []byte(`["EVENT",{"id":"`) raw = raw | mxFill(64, '0') raw = raw | `","pubkey":"` raw = raw | mxFill(64, '0') raw = raw | `","created_at":1700000000,"kind":` raw = raw | itoa64(int64(kind)) raw = raw | `,"tags":` raw = raw | tagsJSON raw = raw | `,"content":"","sig":""}]` return raw } // mxRawEventNoTags omits the tags key, so the parsed event carries nil Tags. func mxRawEventNoTags(kind uint16) (out []byte) { raw := []byte(`["EVENT",{"id":"`) raw = raw | mxFill(64, '0') raw = raw | `","pubkey":"` raw = raw | mxFill(64, '0') raw = raw | `","created_at":1700000000,"kind":` raw = raw | itoa64(int64(kind)) raw = raw | `,"content":"","sig":""}]` return raw } // mxStampOK reports whether the line starts with an HH:MM:SS clock stamp and // the space that follows it. func mxStampOK(line string) (ok bool) { if len(line) < 9 { return false } if line[2] != ':' || line[5] != ':' || line[8] != ' ' { return false } for i := 0; i < 8; i++ { if i == 2 || i == 5 { continue } if line[i] < '0' || line[i] > '9' { return false } } return true } // --- formatters --- func TestHexEnc(t *testing.T) { mxChkS(t, "empty nil", hexEnc(nil), "") mxChkS(t, "empty slice", hexEnc([]byte{:0}), "") mxChkS(t, "zero", hexEnc([]byte{0x00}), "00") mxChkS(t, "ff", hexEnc([]byte{0xff}), "ff") mxChkS(t, "nibbles", hexEnc([]byte{0x00, 0x0f, 0xf0, 0xff}), "000ff0ff") mxChkS(t, "hello", hexEnc([]byte("hello")), "68656c6c6f") all := []byte{:256} for i := range all { all[i] = byte(i) } h := hexEnc(all) mxChkI32(t, "all bytes length", int32(len(h)), 512) mxChkS(t, "all bytes head", h[:4], "0001") mxChkS(t, "all bytes tail", h[508:], "feff") } func TestItoa(t *testing.T) { mxChkS(t, "i64str 0", i64str(0), "0") mxChkS(t, "i64str 7", i64str(7), "7") mxChkS(t, "i64str 10", i64str(10), "10") mxChkS(t, "i64str 123", i64str(123), "123") mxChkS(t, "i64str 1000000", i64str(1000000), "1000000") var maxI int64 = 9223372036854775807 mxChkS(t, "i64str max", i64str(maxI), "9223372036854775807") // Negative values go through a variable: a negative literal passed to an // int64 parameter is zero-extended by this compiler (see mxcover's own // test note in docs/relay-test-coverage.md). var negI int64 = -42 mxChkS(t, "i64str -42", i64str(negI), "-42") // A near-min int64 initializer is not asserted: `var negMax int64 = // -9223372036854775807` arrives as 1 (the magnitude is truncated to 32 bits // before the sign is applied), so i64str renders "1". Positive // 9223372036854775807 is exact. Separate compiler bug; see the report. mxChkS(t, "itoa64 0", itoa64(0), "0") mxChkS(t, "itoa64 42", itoa64(42), "42") mxChkS(t, "itoa64 epoch", itoa64(1700000000), "1700000000") var negI2 int64 = -7 mxChkS(t, "itoa64 -7", itoa64(negI2), "-7") // The two renderers must agree. mxChkS(t, "agree 3600", itoa64(3600), i64str(3600)) var agreeNeg int64 = -12345 mxChkS(t, "agree negative", itoa64(agreeNeg), i64str(agreeNeg)) mxChkI64(t, "agreement value", int64(len(itoa64(maxI))), int64(len(i64str(maxI)))) } // --- path classification --- func TestIsBlossomPath(t *testing.T) { mxChkBool(t, "bare 64-hex", isBlossomPath("/"|mxHex64), true) mxChkBool(t, "with extension", isBlossomPath("/"|mxHex64|".png"), true) mxChkBool(t, "dotted extension", isBlossomPath("/"|mxHex64|".tar.gz"), true) mxChkBool(t, "trailing dot", isBlossomPath("/"|mxHex64|"."), true) mxChkBool(t, "uppercase hex", isBlossomPath("/"|mxFill(64, 'A')), true) mxChkBool(t, "mixed case hex", isBlossomPath("/"|mxFill(63, 'a')|"F"), true) mxChkBool(t, "empty", isBlossomPath(""), false) mxChkBool(t, "short hash", isBlossomPath("/"|mxFill(63, '0')), false) mxChkBool(t, "no leading slash", isBlossomPath(mxHex64), false) mxChkBool(t, "non-hex letter", isBlossomPath("/"|mxFill(63, '0')|"g"), false) mxChkBool(t, "letter after hash", isBlossomPath("/"|mxHex64|"x"), false) mxChkBool(t, "slash in extension", isBlossomPath("/"|mxHex64|".p/ng"), false) } func TestHasPrefixSuffix(t *testing.T) { mxChkBool(t, "prefix yes", hasPrefix("hello", "he"), true) mxChkBool(t, "prefix all", hasPrefix("hello", "hello"), true) mxChkBool(t, "prefix empty", hasPrefix("hello", ""), true) mxChkBool(t, "prefix both empty", hasPrefix("", ""), true) mxChkBool(t, "prefix too long", hasPrefix("he", "hello"), false) mxChkBool(t, "prefix miss", hasPrefix("hello", "x"), false) mxChkBool(t, "suffix yes", hasSuffix("hello", "lo"), true) mxChkBool(t, "suffix all", hasSuffix("hello", "hello"), true) mxChkBool(t, "suffix empty", hasSuffix("hello", ""), true) mxChkBool(t, "suffix too long", hasSuffix("lo", "hello"), false) mxChkBool(t, "suffix miss", hasSuffix("hello", "x"), false) } func TestHasFileExtension(t *testing.T) { known := []string{ ".html", ".htm", ".js", ".mjs", ".css", ".wasm", ".svg", ".png", ".jpg", ".jpeg", ".webp", ".gif", ".ico", ".woff", ".woff2", ".ttf", ".otf", ".json", ".xpi", ".map", ".txt", } for _, ext := range known { mxChkBool(t, "known "|ext, hasFileExtension("file"|ext), true) } mxChkBool(t, "no extension", hasFileExtension("route"), false) mxChkBool(t, "extension alone", hasFileExtension(".txt"), false) mxChkBool(t, "tld wine", hasFileExtension("user.wine"), false) mxChkBool(t, "tld lol", hasFileExtension("relay.lol"), false) mxChkBool(t, "tld dev", hasFileExtension("app.dev"), false) mxChkBool(t, "uppercase", hasFileExtension("file.HTML"), false) mxChkBool(t, "near miss", hasFileExtension("/a/file.jsx"), false) mxChkBool(t, "empty", hasFileExtension(""), false) } func TestProxyAllowedCT(t *testing.T) { mxChkBool(t, "image/png", proxyAllowedCT("image/png"), true) mxChkBool(t, "image/svg+xml", proxyAllowedCT("image/svg+xml"), true) mxChkBool(t, "video/mp4", proxyAllowedCT("video/mp4"), true) mxChkBool(t, "octet-stream", proxyAllowedCT("application/octet-stream"), true) mxChkBool(t, "text/html", proxyAllowedCT("text/html"), false) mxChkBool(t, "json", proxyAllowedCT("application/json"), false) mxChkBool(t, "empty", proxyAllowedCT(""), false) mxChkBool(t, "bare image", proxyAllowedCT("image"), false) mxChkBool(t, "octet with params", proxyAllowedCT("application/octet-stream; charset=x"), false) } // --- static serving and the proxy guards --- func TestServeStatic(t *testing.T) { dir, derr := os.MkdirTemp("", "musiquay-main-static") if derr != nil { t.Fatal(derr) return } defer os.RemoveAll(dir) mxWrite(t, dir|"/index.html", "index") mxWrite(t, dir|"/app.js", "console.log(1)") mxWrite(t, dir|"/style.css", "body{}") mxWrite(t, dir|"/data.bin", "BIN") mxWrite(t, dir|"/signer.xpi", "XPI") mxWrite(t, dir|"/a b.js", "spaced") mxWrite(t, dir|"/page.html", "
hi
") mxWrite(t, dir|"/worker.mjs", "mjs") mxWrite(t, dir|"/data.json", "{}") mxWrite(t, dir|"/pic.svg", "") mxWrite(t, dir|"/pic.png", "PNG") mxWrite(t, dir|"/fav.ico", "ICO") mxWrite(t, dir|"/mod.wasm", "WASM") mxWrite(t, dir|"/pic.webp", "WEBP") mxWrite(t, dir|"/font.woff2", "WOFF2") st, hdr, body := serveStatic(dir, "") mxChkI32(t, "root status", st, 200) mxChkS(t, "root content-type", hdr["Content-Type"], "text/html; charset=utf-8") mxChkS(t, "root body", string(body), "index") mxChkS(t, "root coep", hdr["Cross-Origin-Embedder-Policy"], "require-corp") mxChkS(t, "root corp", hdr["Cross-Origin-Resource-Policy"], "same-origin") st, hdr, body = serveStatic(dir, "/") mxChkI32(t, "slash status", st, 200) mxChkS(t, "slash body", string(body), "index") st, hdr, body = serveStatic(dir, "/app.js") mxChkI32(t, "js status", st, 200) mxChkS(t, "js content-type", hdr["Content-Type"], "application/javascript") mxChkS(t, "js body", string(body), "console.log(1)") st, hdr, body = serveStatic(dir, "/style.css") mxChkS(t, "css content-type", hdr["Content-Type"], "text/css") st, hdr, body = serveStatic(dir, "/page.html") mxChkS(t, "html content-type", hdr["Content-Type"], "text/html; charset=utf-8") st, hdr, body = serveStatic(dir, "/worker.mjs") mxChkS(t, "mjs content-type", hdr["Content-Type"], "application/javascript") st, hdr, body = serveStatic(dir, "/data.json") mxChkS(t, "json content-type", hdr["Content-Type"], "application/json") st, hdr, body = serveStatic(dir, "/pic.svg") mxChkS(t, "svg content-type", hdr["Content-Type"], "image/svg+xml") st, hdr, body = serveStatic(dir, "/pic.png") mxChkS(t, "png content-type", hdr["Content-Type"], "image/png") st, hdr, body = serveStatic(dir, "/fav.ico") mxChkS(t, "ico content-type", hdr["Content-Type"], "image/x-icon") st, hdr, body = serveStatic(dir, "/mod.wasm") mxChkS(t, "wasm content-type", hdr["Content-Type"], "application/wasm") st, hdr, body = serveStatic(dir, "/pic.webp") mxChkS(t, "webp content-type", hdr["Content-Type"], "image/webp") st, hdr, body = serveStatic(dir, "/font.woff2") mxChkS(t, "woff2 content-type", hdr["Content-Type"], "font/woff2") st, hdr, body = serveStatic(dir, "/data.bin") mxChkS(t, "unknown content-type", hdr["Content-Type"], "application/octet-stream") st, hdr, body = serveStatic(dir, "/signer.xpi") mxChkS(t, "xpi content-type", hdr["Content-Type"], "application/x-xpinstall") mxChkS(t, "xpi disposition", hdr["Content-Disposition"], "attachment; filename=\"musiquay-signer.xpi\"") // A path-escaped name is decoded before the read. st, hdr, body = serveStatic(dir, "/a%20b.js") mxChkI32(t, "escaped status", st, 200) mxChkS(t, "escaped body", string(body), "spaced") // A missing file with a known extension is a 404. st, hdr, body = serveStatic(dir, "/missing.png") mxChkI32(t, "missing png status", st, 404) mxChkS(t, "missing png content-type", hdr["Content-Type"], "text/plain") mxChkBool(t, "missing png has a body", len(body) > 0, true) // A missing extensionless route falls back to index.html (SPA routing). st, hdr, body = serveStatic(dir, "/relay.lol/user") mxChkI32(t, "spa status", st, 200) mxChkS(t, "spa body", string(body), "index") // A missing path with an unknown suffix is a route, not a file. st, hdr, body = serveStatic(dir, "/user.wine") mxChkI32(t, "tld route status", st, 200) mxChkS(t, "tld route body", string(body), "index") // The service worker path carries the scope header. if merr := os.MkdirAll(dir|"/$sw", 0755); merr != nil { t.Fatal(merr) } mxWrite(t, dir|"/$sw/wasm-host-sw.mjs", "sw") st, hdr, body = serveStatic(dir, "/$sw/wasm-host-sw.mjs") mxChkI32(t, "sw status", st, 200) mxChkS(t, "sw allowed", hdr["Service-Worker-Allowed"], "/") mxChkS(t, "sw content-type", hdr["Content-Type"], "application/javascript") // With no index.html a missing route is a 404 from the fallback read. empty, eerr := os.MkdirTemp("", "musiquay-main-empty") if eerr != nil { t.Fatal(eerr) return } defer os.RemoveAll(empty) st, hdr, body = serveStatic(empty, "/no-extension") mxChkI32(t, "no index fallback status", st, 404) mxChkBool(t, "no index fallback body", len(body) > 0, true) st, hdr, body = serveStatic(empty, "/missing.png") mxChkI32(t, "empty dir png status", st, 404) } func TestHandleProxyGuards(t *testing.T) { st, hdr, body := handleProxy("", "self.example/") mxChkI32(t, "empty path status", st, 400) mxChkS(t, "empty path content-type", hdr["Content-Type"], "text/plain") mxChkBool(t, "empty path has a body", len(body) > 0, true) st, hdr, body = handleProxy("self.example/foo/bar", "self.example/") mxChkI32(t, "self status", st, 302) mxChkS(t, "self location", hdr["Location"], "/foo/bar") mxChkS(t, "self cors", hdr["Access-Control-Allow-Origin"], "*") mxChkI32(t, "self body length", int32(len(body)), 0) } // --- crawl --- func TestRelayDBSorted(t *testing.T) { db := newRelayDB() mxChkI32(t, "empty sorted", int32(len(db.sorted())), 0) db.add("wss://a.example", 5) db.add("wss://b.example", 3) db.add("wss://a.example", 2) s := db.sorted() mxChkI32(t, "two relays", int32(len(s)), 2) mxChkS(t, "top by summed score", s[0], "wss://a.example") mxChkS(t, "second", s[1], "wss://b.example") mxChkI32(t, "score summed", db.score["wss://a.example"], 7) db2 := newRelayDB() db2.add("c", 1) db2.add("b", 2) db2.add("a", 3) s2 := db2.sorted() mxChkS(t, "descending 0", s2[0], "a") mxChkS(t, "descending 1", s2[1], "b") mxChkS(t, "descending 2", s2[2], "c") // A zero weight still registers the relay. db3 := newRelayDB() db3.add("zero", 0) s3 := db3.sorted() mxChkI32(t, "zero weight kept", int32(len(s3)), 1) mxChkS(t, "zero weight name", s3[0], "zero") // Ten entries exercise the slice presized from the score count. db4 := newRelayDB() names := []string{"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"} for i, nm := range names { db4.add(nm, int32(i)) } s4 := db4.sorted() mxChkI32(t, "ten relays", int32(len(s4)), 10) mxChkS(t, "ten top", s4[0], "n9") mxChkS(t, "ten bottom", s4[9], "n0") } func TestCrawlAppendUniq(t *testing.T) { r := crawlAppendUniq([]string{:0}, "a") mxChkI32(t, "first append", int32(len(r)), 1) mxChkS(t, "first value", r[0], "a") r = crawlAppendUniq(r, "a") mxChkI32(t, "duplicate kept once", int32(len(r)), 1) r = crawlAppendUniq(r, "b") r = crawlAppendUniq(r, "c") mxChkI32(t, "three entries", int32(len(r)), 3) mxChkS(t, "order 0", r[0], "a") mxChkS(t, "order 1", r[1], "b") mxChkS(t, "order 2", r[2], "c") r = crawlAppendUniq(r, "b") mxChkI32(t, "middle duplicate", int32(len(r)), 3) // A preallocated slice with spare capacity accepts the append. preset := []string{:0:4} preset = push(preset, "x") preset = push(preset, "y") p := crawlAppendUniq(preset, "y") mxChkI32(t, "preset duplicate", int32(len(p)), 2) p = crawlAppendUniq(p, "z") mxChkI32(t, "preset append", int32(len(p)), 3) mxChkS(t, "preset order 0", p[0], "x") mxChkS(t, "preset order 1", p[1], "y") mxChkS(t, "preset order 2", p[2], "z") } func TestCrawlExtractRelays(t *testing.T) { db := newRelayDB() raw := mxRawEvent(10002, `[["r","wss://relay.one"],["r","ws://relay.two"]]`) crawlExtractRelays(raw, db) mxChkI32(t, "kind 10002 wss", db.score["wss://relay.one"], 1) mxChkI32(t, "kind 10002 ws", db.score["ws://relay.two"], 1) // A second sighting bumps the score. crawlExtractRelays(raw, db) mxChkI32(t, "score bumped", db.score["wss://relay.one"], 2) // Kind 10050 (relay list metadata) is crawled too. crawlExtractRelays(mxRawEvent(10050, `[["r","wss://relay.fifty"]]`), db) mxChkI32(t, "kind 10050", db.score["wss://relay.fifty"], 1) // Other kinds, non-ws schemes and one-element tags are ignored. before := int32(len(db.score)) crawlExtractRelays(mxRawEvent(1, `[["r","wss://relay.one"]]`), db) mxChkI32(t, "kind 1 ignored", int32(len(db.score)), before) crawlExtractRelays(mxRawEvent(10002, `[["r","https://relay.one"],["r","wss:"],["r"]]`), db) mxChkI32(t, "bad schemes and short tags ignored", int32(len(db.score)), before) // A nil tag list and a malformed frame leave the db alone. crawlExtractRelays(mxRawEventNoTags(10002), db) mxChkI32(t, "nil tags ignored", int32(len(db.score)), before) crawlExtractRelays([]byte("garbage"), db) mxChkI32(t, "malformed frame ignored", int32(len(db.score)), before) } // crawlPublishBatch's dial-error branch: an unreachable local URL returns 0 // without touching the (nil) connection. The batch is deliberately non-empty // so the empty-batch path is not what is being measured. func TestCrawlPublishBatchDialError(t *testing.T) { f, ferr := os.CreateTemp("", "musiquay-pub") if ferr != nil { t.Fatal(ferr) return } defer os.Remove(f.Name()) defer f.Close() evs := [][]byte{:0} evs = push(evs, []byte("[]")) n := crawlPublishBatch("ws://127.0.0.1:1", evs, f) mxChkI32(t, "dial failure publishes nothing", n, 0) } func TestClog(t *testing.T) { dir, derr := os.MkdirTemp("", "musiquay-main-clog") if derr != nil { t.Fatal(derr) return } defer os.RemoveAll(dir) path := dir | "/crawl.log" f, ferr := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if ferr != nil { t.Fatal(ferr) return } clog(f, "started pid=%d local=%s", os.Getpid(), "ws://127.0.0.1:3334") clog(f, "=== pass 1 ===") f.Close() data, rerr := os.ReadFile(path) if rerr != nil { t.Fatal(rerr) return } line := string(data) mxChkBool(t, "starts with a timestamp", mxStampOK(line), true) mxChkBool(t, "has a body", len(line) > 9, true) if len(line) > 9 { mxChkBool(t, "body prefix", hasPrefix(line[9:], "started pid="), true) } // The trailing-newline assertion is left out: `moxie test` miscompiles the // "\n" literal in the tested package, so clog's line terminator cannot be // pinned here (harness bug, not a main.mx bug). }