2021-01-26 17:33:29 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
// Package nodejs_test contains test scenarios and helpers to run interoparability tests against the javascript implementation.
|
2021-01-25 10:39:05 +00:00
|
|
|
package nodejs_test
|
2021-01-26 17:33:29 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"context"
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
2021-01-27 09:27:13 +00:00
|
|
|
"errors"
|
2021-01-26 17:33:29 +00:00
|
|
|
"fmt"
|
2021-03-17 14:01:34 +00:00
|
|
|
"io"
|
2021-01-26 17:33:29 +00:00
|
|
|
mrand "math/rand"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
2021-03-17 14:01:34 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
2021-01-26 17:33:29 +00:00
|
|
|
|
2021-03-10 15:44:46 +00:00
|
|
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
2021-02-15 11:08:33 +00:00
|
|
|
|
2021-01-26 17:33:29 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
|
|
|
"github.com/go-kit/kit/log"
|
2021-01-26 18:03:31 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2021-01-26 17:33:29 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.cryptoscope.co/muxrpc/v2/debug"
|
2021-01-27 09:02:27 +00:00
|
|
|
"go.cryptoscope.co/netwrap"
|
2021-01-26 17:33:29 +00:00
|
|
|
refs "go.mindeco.de/ssb-refs"
|
|
|
|
|
2021-02-09 16:38:51 +00:00
|
|
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/testutils"
|
|
|
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomsrv"
|
2021-01-26 17:33:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
err := os.RemoveAll("testrun")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("failed to clean testrun dir")
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type testSession struct {
|
|
|
|
t *testing.T
|
|
|
|
|
|
|
|
info log.Logger
|
|
|
|
|
|
|
|
repo string
|
|
|
|
|
2021-01-27 09:02:27 +00:00
|
|
|
keySHS []byte
|
2021-01-26 17:33:29 +00:00
|
|
|
|
|
|
|
done errgroup.Group
|
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: restrucuture so that we can test both (default and random net keys) with the same Code
|
|
|
|
|
|
|
|
// rolls random values for secret-handshake app-key and HMAC
|
|
|
|
func newRandomSession(t *testing.T) *testSession {
|
|
|
|
appKey := make([]byte, 32)
|
|
|
|
rand.Read(appKey)
|
2021-01-27 09:02:27 +00:00
|
|
|
return newSession(t, appKey)
|
2021-01-26 17:33:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// if appKey is nil, the default value is used
|
|
|
|
// if hmac is nil, the object string is signed instead
|
2021-01-27 09:02:27 +00:00
|
|
|
func newSession(t *testing.T, appKey []byte) *testSession {
|
2021-01-26 17:33:29 +00:00
|
|
|
repo := filepath.Join("testrun", t.Name())
|
|
|
|
err := os.RemoveAll(repo)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("remove testrun folder (%s) for this test: %s", repo, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ts := &testSession{
|
2021-01-27 09:02:27 +00:00
|
|
|
info: testutils.NewRelativeTimeLogger(nil),
|
|
|
|
repo: repo,
|
|
|
|
t: t,
|
|
|
|
keySHS: appKey,
|
2021-01-26 17:33:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// todo: hook into deadline
|
|
|
|
ts.ctx, ts.cancel = context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
return ts
|
|
|
|
}
|
|
|
|
|
2021-03-11 17:40:33 +00:00
|
|
|
func (ts *testSession) startGoServer(
|
2021-03-19 10:51:06 +00:00
|
|
|
membersDB roomdb.MembersService,
|
|
|
|
aliasDB roomdb.AliasesService,
|
2021-03-17 14:01:34 +00:00
|
|
|
opts ...roomsrv.Option,
|
|
|
|
) *roomsrv.Server {
|
2021-01-26 17:33:29 +00:00
|
|
|
r := require.New(ts.t)
|
|
|
|
|
|
|
|
// prepend defaults
|
|
|
|
opts = append([]roomsrv.Option{
|
|
|
|
roomsrv.WithLogger(ts.info),
|
|
|
|
roomsrv.WithListenAddr("localhost:0"),
|
|
|
|
roomsrv.WithRepoPath(ts.repo),
|
|
|
|
roomsrv.WithContext(ts.ctx),
|
|
|
|
}, opts...)
|
|
|
|
|
|
|
|
if ts.keySHS != nil {
|
|
|
|
opts = append(opts, roomsrv.WithAppKey(ts.keySHS))
|
|
|
|
}
|
|
|
|
|
|
|
|
opts = append(opts,
|
|
|
|
roomsrv.WithPostSecureConnWrapper(func(conn net.Conn) (net.Conn, error) {
|
2021-03-17 14:01:34 +00:00
|
|
|
ref, err := network.GetFeedRefFromAddr(conn.RemoteAddr())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fname := filepath.Join("testrun", ts.t.Name(), "muxdump", ref.ShortRef())
|
|
|
|
return debug.WrapDump(fname, conn)
|
2021-01-26 17:33:29 +00:00
|
|
|
}),
|
|
|
|
)
|
|
|
|
|
2021-03-19 10:51:06 +00:00
|
|
|
srv, err := roomsrv.New(membersDB, aliasDB, opts...)
|
2021-01-26 17:33:29 +00:00
|
|
|
r.NoError(err, "failed to init tees a server")
|
2021-01-27 09:27:13 +00:00
|
|
|
ts.t.Logf("go server: %s", srv.Whoami().Ref())
|
2021-01-26 17:33:29 +00:00
|
|
|
ts.t.Cleanup(func() {
|
2021-01-27 09:27:13 +00:00
|
|
|
ts.t.Log("bot close:", srv.Close())
|
2021-01-26 17:33:29 +00:00
|
|
|
})
|
2021-01-27 09:27:13 +00:00
|
|
|
|
2021-01-26 17:33:29 +00:00
|
|
|
ts.done.Go(func() error {
|
|
|
|
err := srv.Network.Serve(ts.ctx)
|
2021-01-27 09:27:13 +00:00
|
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
|
|
|
err = fmt.Errorf("go server exited: %w", err)
|
2021-01-26 17:33:29 +00:00
|
|
|
ts.t.Log(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2021-01-26 18:03:31 +00:00
|
|
|
return srv
|
2021-01-26 17:33:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var jsBotCnt = 0
|
|
|
|
|
2021-03-17 14:01:34 +00:00
|
|
|
// starts a node process in the client role. returns the jsbots pubkey
|
|
|
|
func (ts *testSession) startJSClient(
|
|
|
|
name,
|
|
|
|
testScript string,
|
|
|
|
// the perr the client should connect to at first (here usually the room server)
|
|
|
|
peerAddr net.Addr,
|
|
|
|
peerRef refs.FeedRef,
|
|
|
|
) refs.FeedRef {
|
2021-01-26 17:33:29 +00:00
|
|
|
ts.t.Log("starting client", name)
|
|
|
|
r := require.New(ts.t)
|
2021-01-27 12:44:17 +00:00
|
|
|
cmd := exec.CommandContext(ts.ctx, "node", "../../../sbot_client.js")
|
2021-01-26 17:33:29 +00:00
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
outrc, err := cmd.StdoutPipe()
|
|
|
|
r.NoError(err)
|
|
|
|
|
|
|
|
if name == "" {
|
2021-01-27 12:44:17 +00:00
|
|
|
name = fmt.Sprintf("jsbot-%d", jsBotCnt)
|
2021-01-26 17:33:29 +00:00
|
|
|
}
|
|
|
|
jsBotCnt++
|
2021-01-27 09:02:27 +00:00
|
|
|
|
2021-01-27 12:44:17 +00:00
|
|
|
// copy test scripts (maybe later with templates if we need to)
|
|
|
|
cmd.Dir = filepath.Join("testrun", ts.t.Name(), name)
|
|
|
|
os.MkdirAll(cmd.Dir, 0700)
|
|
|
|
err = exec.Command("cp", "-r", "testscripts", cmd.Dir).Run()
|
|
|
|
r.NoError(err)
|
2021-01-27 09:27:13 +00:00
|
|
|
|
2021-01-26 17:33:29 +00:00
|
|
|
env := []string{
|
|
|
|
"TEST_NAME=" + name,
|
2021-01-27 12:44:17 +00:00
|
|
|
"TEST_REPO=" + cmd.Dir,
|
2021-01-27 09:27:13 +00:00
|
|
|
"TEST_PEERADDR=" + netwrap.GetAddr(peerAddr, "tcp").String(),
|
|
|
|
"TEST_PEERREF=" + peerRef.Ref(),
|
|
|
|
"TEST_SESSIONSCRIPT=" + testScript,
|
2021-01-27 14:42:20 +00:00
|
|
|
// "DEBUG=ssb:room:tunnel:*",
|
2021-01-26 17:33:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ts.keySHS != nil {
|
|
|
|
env = append(env, "TEST_APPKEY="+base64.StdEncoding.EncodeToString(ts.keySHS))
|
|
|
|
}
|
2021-01-27 09:02:27 +00:00
|
|
|
|
2021-01-26 17:33:29 +00:00
|
|
|
cmd.Env = env
|
2021-03-17 14:01:34 +00:00
|
|
|
|
|
|
|
started := time.Now()
|
2021-01-26 17:33:29 +00:00
|
|
|
r.NoError(cmd.Start(), "failed to init test js-sbot")
|
|
|
|
|
2021-01-26 18:03:31 +00:00
|
|
|
ts.done.Go(func() error {
|
|
|
|
err := cmd.Wait()
|
2021-03-17 14:01:34 +00:00
|
|
|
ts.t.Logf("node client %s: exited with %v (after %s)", name, err, time.Since(started))
|
|
|
|
// we need to return the error code to have an idea if any of the tape assertions failed
|
2021-01-26 18:03:31 +00:00
|
|
|
if err != nil {
|
2021-03-17 14:01:34 +00:00
|
|
|
return fmt.Errorf("node client %s exited with %s", name, err)
|
2021-01-26 18:03:31 +00:00
|
|
|
}
|
2021-03-17 14:01:34 +00:00
|
|
|
return nil
|
2021-01-26 18:03:31 +00:00
|
|
|
})
|
2021-01-26 17:33:29 +00:00
|
|
|
ts.t.Cleanup(func() {
|
|
|
|
cmd.Process.Kill()
|
|
|
|
})
|
|
|
|
|
|
|
|
pubScanner := bufio.NewScanner(outrc) // TODO muxrpc comms?
|
|
|
|
r.True(pubScanner.Scan(), "multiple lines of output from js - expected #1 to be %s pubkey/id", name)
|
|
|
|
|
2021-03-17 14:01:34 +00:00
|
|
|
go io.Copy(os.Stderr, outrc) // restore node stdout to stderr behavior
|
|
|
|
|
2021-01-26 17:33:29 +00:00
|
|
|
jsBotRef, err := refs.ParseFeedRef(pubScanner.Text())
|
|
|
|
r.NoError(err, "failed to get %s key from JS process")
|
|
|
|
ts.t.Logf("JS %s:%d %s", name, jsBotCnt, jsBotRef.Ref())
|
|
|
|
return *jsBotRef
|
|
|
|
}
|
|
|
|
|
2021-03-17 14:01:34 +00:00
|
|
|
// startJSBotAsServer returns the servers public key and it's TCP port on localhost.
|
|
|
|
// This is only here to check compliance against the old javascript server.
|
|
|
|
// We don't care so much about it's internal behavior, just that clients can connect through it.
|
2021-01-26 17:33:29 +00:00
|
|
|
func (ts *testSession) startJSBotAsServer(name, testScriptFileName string) (*refs.FeedRef, int) {
|
|
|
|
r := require.New(ts.t)
|
2021-01-27 12:44:17 +00:00
|
|
|
cmd := exec.CommandContext(ts.ctx, "node", "../../../sbot_serv.js")
|
2021-01-26 17:33:29 +00:00
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
outrc, err := cmd.StdoutPipe()
|
|
|
|
r.NoError(err)
|
|
|
|
|
|
|
|
if name == "" {
|
|
|
|
name = fmt.Sprintf("jsbot-%d", jsBotCnt)
|
|
|
|
}
|
|
|
|
jsBotCnt++
|
|
|
|
|
2021-01-27 12:44:17 +00:00
|
|
|
// copy test scripts (maybe later with templates if we need to)
|
|
|
|
cmd.Dir = filepath.Join("testrun", ts.t.Name(), name)
|
|
|
|
os.MkdirAll(cmd.Dir, 0700)
|
|
|
|
err = exec.Command("cp", "-r", "testscripts", cmd.Dir).Run()
|
|
|
|
r.NoError(err)
|
|
|
|
|
2021-01-26 17:33:29 +00:00
|
|
|
var port = 1024 + mrand.Intn(23000)
|
|
|
|
|
|
|
|
env := []string{
|
2021-01-27 12:44:17 +00:00
|
|
|
"TEST_NAME=jsbot-" + name,
|
|
|
|
"TEST_REPO=" + cmd.Dir,
|
2021-01-26 17:33:29 +00:00
|
|
|
fmt.Sprintf("TEST_PORT=%d", port),
|
2021-01-27 09:27:13 +00:00
|
|
|
"TEST_SESSIONSCRIPT=" + testScriptFileName,
|
2021-01-27 14:42:20 +00:00
|
|
|
// "DEBUG=ssb:room:tunnel:*",
|
2021-01-26 17:33:29 +00:00
|
|
|
}
|
2021-01-27 09:02:27 +00:00
|
|
|
if ts.keySHS != nil {
|
|
|
|
env = append(env, "TEST_APPKEY="+base64.StdEncoding.EncodeToString(ts.keySHS))
|
|
|
|
}
|
2021-01-26 17:33:29 +00:00
|
|
|
cmd.Env = env
|
|
|
|
|
2021-03-17 14:01:34 +00:00
|
|
|
started := time.Now()
|
2021-01-26 17:33:29 +00:00
|
|
|
r.NoError(cmd.Start(), "failed to init test js-sbot")
|
|
|
|
|
2021-01-26 18:03:31 +00:00
|
|
|
ts.done.Go(func() error {
|
|
|
|
err := cmd.Wait()
|
2021-03-17 14:01:34 +00:00
|
|
|
ts.t.Logf("node server %s: exited with %v (after %s)", name, err, time.Since(started))
|
|
|
|
return nil
|
2021-01-26 18:03:31 +00:00
|
|
|
})
|
2021-01-26 17:33:29 +00:00
|
|
|
ts.t.Cleanup(func() {
|
|
|
|
cmd.Process.Kill()
|
|
|
|
})
|
|
|
|
|
|
|
|
pubScanner := bufio.NewScanner(outrc) // TODO muxrpc comms?
|
|
|
|
r.True(pubScanner.Scan(), "multiple lines of output from js - expected #1 to be %s pubkey/id", name)
|
|
|
|
|
|
|
|
srvRef, err := refs.ParseFeedRef(pubScanner.Text())
|
|
|
|
r.NoError(err, "failed to get srvRef key from JS process")
|
|
|
|
ts.t.Logf("JS %s: %s port: %d", name, srvRef.Ref(), port)
|
|
|
|
return srvRef, port
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ts *testSession) wait() {
|
2021-01-26 18:03:31 +00:00
|
|
|
ts.cancel()
|
2021-01-26 17:33:29 +00:00
|
|
|
|
2021-01-26 18:03:31 +00:00
|
|
|
assert.NoError(ts.t, ts.done.Wait())
|
2021-01-26 17:33:29 +00:00
|
|
|
}
|