server_e2e_test.go (4543B)
1 package tui 2 3 import ( 4 "context" 5 "crypto/ed25519" 6 "crypto/rand" 7 "os" 8 "strings" 9 "testing" 10 "time" 11 12 "git.ryansepassi.com/git/gotextlog.git/internal/sshkey" 13 "git.ryansepassi.com/git/gotextlog.git/internal/textlog" 14 "golang.org/x/crypto/ssh" 15 ) 16 17 type generatedKeySource struct { 18 key sshkey.Key 19 signer ssh.Signer 20 } 21 22 func newGeneratedKeySource(t *testing.T) *generatedKeySource { 23 t.Helper() 24 _, privateKey, err := ed25519.GenerateKey(rand.Reader) 25 if err != nil { 26 t.Fatal(err) 27 } 28 signer, err := ssh.NewSignerFromKey(privateKey) 29 if err != nil { 30 t.Fatal(err) 31 } 32 public := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(signer.PublicKey()))) 33 return &generatedKeySource{ 34 key: sshkey.Key{PublicKey: public, Fingerprint: ssh.FingerprintSHA256(signer.PublicKey())}, 35 signer: signer, 36 } 37 } 38 39 func (s *generatedKeySource) Keys(context.Context) ([]sshkey.Key, error) { 40 return []sshkey.Key{s.key}, nil 41 } 42 43 func (s *generatedKeySource) Sign(_ context.Context, _ sshkey.Key, message []byte) ([]byte, error) { 44 signature, err := s.signer.Sign(rand.Reader, message) 45 if err != nil { 46 return nil, err 47 } 48 return ssh.Marshal(signature), nil 49 } 50 51 func TestServerEndToEndWithTwoTUIBackends(t *testing.T) { 52 baseURL := os.Getenv("GOTEXTLOG_TEST_SERVER_URL") 53 if baseURL == "" { 54 t.Skip("set GOTEXTLOG_TEST_SERVER_URL to run against gotextlog-srv") 55 } 56 ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 57 defer cancel() 58 59 aliceKeys := newGeneratedKeySource(t) 60 bobKeys := newGeneratedKeySource(t) 61 alice := NewClientBackendWithKeySource(textlog.NewClient(baseURL, "", nil), aliceKeys) 62 bob := NewClientBackendWithKeySource(textlog.NewClient(baseURL, "", nil), bobKeys) 63 64 aliceLogin, err := alice.Execute(ctx, Operation{Kind: OpKeyLogin, Key: aliceKeys.key, Handle: "alice"}) 65 if err != nil || aliceLogin.Me == nil || aliceLogin.Me.Handle != "alice" { 66 t.Fatalf("register alice: result=%#v err=%v", aliceLogin, err) 67 } 68 bobLogin, err := bob.Execute(ctx, Operation{Kind: OpKeyLogin, Key: bobKeys.key, Handle: "bob"}) 69 if err != nil || bobLogin.Me == nil || bobLogin.Me.Handle != "bob" { 70 t.Fatalf("register bob: result=%#v err=%v", bobLogin, err) 71 } 72 alice = NewClientBackendWithKeySource(textlog.NewClient(baseURL, "", nil), aliceKeys) 73 aliceLogin, err = alice.Execute(ctx, Operation{Kind: OpKeyLogin, Key: aliceKeys.key}) 74 if err != nil || aliceLogin.Me == nil || aliceLogin.Me.Handle != "alice" { 75 t.Fatalf("login existing alice key: result=%#v err=%v", aliceLogin, err) 76 } 77 78 if _, err := bob.Execute(ctx, Operation{Kind: OpFollow, Handle: "alice", Enabled: true}); err != nil { 79 t.Fatalf("bob follows alice: %v", err) 80 } 81 created, err := alice.Execute(ctx, Operation{Kind: OpCreatePost, Text: "hello from #golang to @bob"}) 82 if err != nil || created.Post == nil { 83 t.Fatalf("alice creates post: result=%#v err=%v", created, err) 84 } 85 rootID := created.Post.ID 86 reply, err := bob.Execute(ctx, Operation{Kind: OpCreatePost, Text: "hello back", ParentID: &rootID}) 87 if err != nil || reply.Post == nil || reply.Post.ParentID == nil || *reply.Post.ParentID != rootID { 88 t.Fatalf("bob replies: result=%#v err=%v", reply, err) 89 } 90 91 thread, err := alice.Execute(ctx, Operation{Kind: OpThread, ID: rootID}) 92 if err != nil || len(thread.Replies) != 1 || thread.Replies[0].ID != reply.Post.ID { 93 t.Fatalf("thread: result=%#v err=%v", thread, err) 94 } 95 feed, err := bob.Execute(ctx, Operation{Kind: OpFeed, Feed: "for-you"}) 96 if err != nil || len(feed.Entries) == 0 { 97 t.Fatalf("bob for-you feed: result=%#v err=%v", feed, err) 98 } 99 toAlice, err := alice.Execute(ctx, Operation{Kind: OpFeed, Feed: "to-me"}) 100 if err != nil || len(toAlice.Entries) < 2 { 101 t.Fatalf("alice activity: result=%#v err=%v", toAlice, err) 102 } 103 search, err := bob.Execute(ctx, Operation{Kind: OpSearch, Query: "golang"}) 104 if err != nil || len(search.Posts) != 1 || search.Posts[0].ID != rootID { 105 t.Fatalf("search: result=%#v err=%v", search, err) 106 } 107 profile, err := bob.Execute(ctx, Operation{Kind: OpProfile, Handle: "alice"}) 108 if err != nil || profile.Profile == nil || profile.Profile.PostCount != 1 { 109 t.Fatalf("profile: result=%#v err=%v", profile, err) 110 } 111 if _, err := bob.Execute(ctx, Operation{Kind: OpBlock, Handle: "alice", Enabled: true}); err != nil { 112 t.Fatalf("bob blocks alice: %v", err) 113 } 114 if _, err := bob.Execute(ctx, Operation{Kind: OpRevoke}); err != nil { 115 t.Fatalf("bob revokes session: %v", err) 116 } 117 if _, err := bob.Execute(ctx, Operation{Kind: OpMe}); err == nil { 118 t.Fatal("revoked session still authenticated") 119 } 120 }