mirror of
https://github.com/p1neappleXpress/OpenFlux.git
synced 2026-10-02 05:04:39 +08:00
* share: links are read and made in the core, with codes for the apps share.Read and share.Make answer every entry point with the same JSON (--parse-link, new --make-link, mobile.ReadShareLink / MakeShareLink, the iOS OpenFluxShareDecode / OpenFluxShareEncode): the configuration and its context, the link, or an error code and param. Every link problem now has a code (share.Code*); the apps word it for their users, the core only decides which one it is. Error() keeps the English detail for the CLI. share.Make normalizes what apps spell differently: surrounding spaces, the default codec, a carrier named after its type, and the context of an encrypted link, filled in by transport.KDFContexts when not given. One configuration gives one link whichever client exports it. The node wizard's link (desktop and Android) comes from one share.NodeConfig, and --share and ExitShareLink go through share.Make too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * share: a lone carrier's link carries no priority Apps that keep a priority for a single carrier and apps that do not now export the same link for the same profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: p1neappleXpress <a@a.a> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
4.0 KiB
Go
95 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"openflux/share"
|
|
)
|
|
|
|
// The link an exit prints must decode to what a client needs: the exit's
|
|
// transports, key and context, with direct dialing the shared host.
|
|
func TestShareConfigFromExitSpecs(t *testing.T) {
|
|
specs := []transportSpec{
|
|
{Name: "direct", Type: "direct", Priority: 100, Params: map[string]interface{}{"listen": "0.0.0.0:8445", "is_exit": true}},
|
|
{Name: "yandex", Type: "yandex", Priority: 50, URL: "https://disk.yandex.ru/i/abc"},
|
|
{Name: "oneme", Type: "oneme", Priority: 10},
|
|
}
|
|
c, skipped := shareConfig(specs, true, codecBatched, "a shared secret of 32 characters", "https://disk.yandex.ru/i/abc", "203.0.113.7", nil)
|
|
if len(skipped) != 1 || !strings.HasPrefix(skipped[0], "oneme") {
|
|
t.Fatalf("skipped = %v, want the MAX transport only", skipped)
|
|
}
|
|
link, err := share.Encode(c)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := share.Decode(link)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !got.Negotiate || got.Context != "https://disk.yandex.ru/i/abc" || len(got.Transports) != 2 ||
|
|
got.Transports[0].Dial != "203.0.113.7:8445" || got.Transports[1].URL != "https://disk.yandex.ru/i/abc" {
|
|
t.Fatalf("decoded %+v", got)
|
|
}
|
|
|
|
_, skipped = shareConfig(specs[:1], true, codecBatched, "a shared secret of 32 characters", "", "", nil)
|
|
if len(skipped) != 1 {
|
|
t.Fatal("direct without a host to share must be left out")
|
|
}
|
|
}
|
|
|
|
type fakeRooms struct{ list string }
|
|
|
|
func (f *fakeRooms) RoomList() string { return f.list }
|
|
func (f *fakeRooms) OnRoomList(func(string)) {}
|
|
|
|
// A cupsonline exit started without --url creates its rooms at start; the
|
|
// link must carry them, or the client has nothing to join.
|
|
func TestShareConfigCarriesCreatedCupsRooms(t *testing.T) {
|
|
specs := []transportSpec{{Name: "cupsonline", Type: "cupsonline", Priority: 50}}
|
|
rooms := map[string]roomLister{"cupsonline": &fakeRooms{}}
|
|
|
|
_, skipped := shareConfig(specs, true, codecBatched, "a shared secret of 32 characters", "http://#", "", rooms)
|
|
if len(skipped) != 1 || !strings.HasPrefix(skipped[0], "cupsonline") {
|
|
t.Fatalf("skipped = %v, want cupsonline left out before its rooms exist", skipped)
|
|
}
|
|
|
|
rooms["cupsonline"].(*fakeRooms).list = "WyJyb29tLTEiXQ"
|
|
c, skipped := shareConfig(specs, true, codecBatched, "a shared secret of 32 characters", "http://#", "", rooms)
|
|
if len(skipped) != 0 || len(c.Transports) != 1 || c.Transports[0].URL != "WyJyb29tLTEiXQ" {
|
|
t.Fatalf("transports %+v, skipped %v", c.Transports, skipped)
|
|
}
|
|
}
|
|
|
|
// --parse-link and --make-link answer with share.Result, byte for byte
|
|
// what package mobile and the iOS library answer, and the wizard's link is
|
|
// the one share.Make builds for its configuration.
|
|
func TestLinkCommandsAnswerLikeShare(t *testing.T) {
|
|
cfg := `{"name":"node","negotiate":true,"secret":"a shared secret of 32 characters",` +
|
|
`"transports":[{"type":"vyandex","url":"https://docs.yandex.ru/edit/d/AbC","priority":100}]}`
|
|
var out strings.Builder
|
|
if code := runMakeLink("-", strings.NewReader(cfg), &out); code != 0 {
|
|
t.Fatalf("--make-link exit %d: %s", code, out.String())
|
|
}
|
|
made := share.MakeJSON(cfg)
|
|
if out.String() != made.JSON()+"\n" {
|
|
t.Fatalf("--make-link %s, share.MakeJSON %s", out.String(), made.JSON())
|
|
}
|
|
|
|
wrapped := made.Link[:40] + "\r\n" + made.Link[40:]
|
|
out.Reset()
|
|
if code := runParseLink("-", strings.NewReader(wrapped), &out); code != 0 || out.String() != share.Read(made.Link).JSON()+"\n" {
|
|
t.Fatalf("--parse-link exit %d: %s", code, out.String())
|
|
}
|
|
out.Reset()
|
|
if code := runParseLink("https://example.com", nil, &out); code != 1 || !strings.Contains(out.String(), `"code":"not_link"`) {
|
|
t.Fatalf("--parse-link on garbage: exit %d %s", code, out.String())
|
|
}
|
|
|
|
link, err := nodeShareLink("node", "https://docs.yandex.ru/edit/d/AbC", "a shared secret of 32 characters", "203.0.113.7", 9443)
|
|
want := share.Make(share.NodeConfig("node", "https://docs.yandex.ru/edit/d/AbC", "a shared secret of 32 characters", "203.0.113.7:9443"))
|
|
if err != nil || link != want.Link {
|
|
t.Fatalf("wizard link %q (%v), share.Make %q", link, err, want.Link)
|
|
}
|
|
}
|