func TestTemplateEmailer(t *testing.T) { Convey("TemplateEmailer", t, func() { tmpl, err := template.New("test").Parse(` {{define "test.html"}}html part{{end}} {{define "test.txt"}}text part{{end}} {{define "test.hdr"}}Subject: test{{end}}`) So(err, ShouldBeNil) e := &TemplateEmailer{ Deliverer: &TestDeliverer{}, Templater: &templates.Templater{Templates: map[string]*template.Template{"test": tmpl}}, } Convey("Send test email", func() { c := e.Deliverer.(MockDeliverer).Inbox("*****@*****.**") msgID, err := e.Send(scope.New(), "*****@*****.**", "test", nil) So(err, ShouldBeNil) email := parseEmail(<-c) So(email.Header.Get("Message-ID"), ShouldEqual, msgID) So(email.Header.Get("Subject"), ShouldEqual, "test") So(string(email.Text), ShouldEqual, "text part") So(string(email.HTML), ShouldEqual, "html part") }) }) }
func main() { go func() { // goto: http://localhost:6060/debug/pprof/ for goroutine info! fmt.Println(http.ListenAndServe("localhost:6060", nil)) }() bcfg := babel.BotConfig{ Nick: "babelbot", RoomName: "test", Cmds: []plugin.CmdPluginDialer{ plugin.CmdPluginDialer{ Cmd: "python", Args: []string{"log_everything.py"}, }, }, } ws := &connection.WSConnectionDialer{ UrlFormat: connection.EUPHORIA_URL_FORMAT, } ctx := scope.New() proto.SetLogger(ctx, logrus.StandardLogger()) err := bcfg.Start(ctx, ws) if err != nil { proto.GetLogger(ctx).Errorf("bcfg.Start failure in main(): %s", err) } }
func TestRunHandler(t *testing.T) { ctrl := &Controller{} ctx := scope.New() Convey("Successfully runs", t, func() { term := &testTerm{} runHandler(ctx, testHandler{}, cmdConsole(ctrl, "test", term), []string{"test"}) So(term.String(), ShouldEqual, "ok\r\n") }) Convey("Usage error", t, func() { Convey("Handler serves usage", func() { term := &testTerm{} runHandler(ctx, testHandlerWithUsage{}, cmdConsole(ctrl, "test", term), nil) So(term.String(), ShouldEqual, "error: invalid number of arguments: 0\r\nusage\r\n\r\nOPTIONS:\r\n") }) Convey("Handler doesn't serve usage", func() { term := &testTerm{} runHandler(ctx, testHandler{}, cmdConsole(ctrl, "test", term), nil) So(term.String(), ShouldEqual, "error: invalid number of arguments: 0\r\n") }) }) }
func TestRoomPresence(t *testing.T) { userA := newSession("A", "A1", "ip1") userA2 := newSession("A", "A2", "ip2") userB := newSession("B", "B1", "ip3") ctx := scope.New() kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) roomp, err := NewRoom(ctx, kms, false, "test", "testver") if err != nil { t.Fatal(err) } room := roomp.(*memRoom) client := &proto.Client{Agent: &proto.Agent{}} client.FromRequest(ctx, &http.Request{}) Convey("First join", t, func() { _, err := room.Join(ctx, userA) So(err, ShouldBeNil) So(room.identities, ShouldResemble, map[proto.UserID]proto.Identity{"A": userA.Identity()}) So(room.live, ShouldResemble, map[proto.UserID][]proto.Session{"A": []proto.Session{userA}}) }) Convey("Second join", t, func() { _, err := room.Join(ctx, userB) So(err, ShouldBeNil) So(room.identities["B"], ShouldResemble, userB.Identity()) So(room.live["B"], ShouldResemble, []proto.Session{userB}) }) Convey("Duplicate join", t, func() { _, err := room.Join(ctx, userA2) So(err, ShouldBeNil) So(room.live["A"], ShouldResemble, []proto.Session{userA, userA2}) }) Convey("Deduplicate part", t, func() { So(room.Part(ctx, userA), ShouldBeNil) So(room.identities["A"], ShouldResemble, userA.Identity()) So(room.live["A"], ShouldResemble, []proto.Session{userA2}) }) Convey("More parts", t, func() { So(room.Part(ctx, userA2), ShouldBeNil) So(room.identities["A"], ShouldBeNil) So(room.live["A"], ShouldBeNil) So(room.Part(ctx, userB), ShouldBeNil) So(room.identities["B"], ShouldBeNil) So(room.live["B"], ShouldBeNil) }) }
func (s *EtcdServer) Join(root, id, era string) cluster.Cluster { desc := &cluster.PeerDesc{ ID: id, Era: era, } c, err := etcd.EtcdCluster(scope.New(), root, s.addr, desc) if err != nil { panic(fmt.Sprintf("error joining cluster: %s", err)) } return c }
func TestBackend(t *testing.T) { etcd, err := clustertest.StartEtcd() if err != nil { t.Fatal(err) } if etcd == nil { t.Fatal("etcd not available in PATH, can't test backend") } defer etcd.Shutdown() dsn := *dsn if env := os.Getenv("DSN"); env != "" { // for running in CI container dsn = env } db, err := sql.Open("postgres", dsn) if err != nil { t.Fatalf("sql.Open: %s", err) } // Drop all tables. for _, item := range schema { if _, err := db.Exec("DROP TABLE IF EXISTS " + item.Name + " CASCADE"); err != nil { t.Fatalf("failed to drop table %s: %s", item.Name, err) } } if _, err := db.Exec("DROP TABLE IF EXISTS gorp_migrations"); err != nil { t.Fatal(err) } // Recreate all tables. src := migrate.FileMigrationSource{"migrations"} if _, err := migrate.Exec(db, "postgres", src, migrate.Up); err != nil { t.Fatal(err) } // Start up backend. c := etcd.Join("/test", "testcase", "era") desc := &cluster.PeerDesc{ ID: "testcase", Era: "era", Version: "testver", } b, err := NewBackend(scope.New(), dsn, c, desc) if err != nil { t.Fatal(err) } defer b.Close() // Run test suite. backend.IntegrationTest(t, func() proto.Backend { return nonClosingBackend{b} }) }
func main() { logger := &log.Logger{ Handler: cli.New(os.Stdout), Level: log.DebugLevel, } ctx := scope.New() ctx.Set("logger", logger) b := bot.NewBot(ctx, []proto.Handler{&handlers.PingHandler{}, &handlers.PongHandler{}}, connection.NewWSDialer(ctx.Fork(), fmt.Sprintf(url, "test"))) if err := b.Run(); err != nil { logger.Fatalf("Bot.Run: Fatal error (%s)", err) } }
func (s *Server) handleRoomStatic(w http.ResponseWriter, r *http.Request) { if !s.allowRoomCreation { roomName := mux.Vars(r)["room"] _, err := s.b.GetRoom(scope.New(), roomName) if err != nil { if err == proto.ErrRoomNotFound { http.Error(w, "404 page not found", http.StatusNotFound) return } } } s.serveGzippedFile(w, r, "index.html", false) }
func TestMemLogLatest(t *testing.T) { ctx := scope.New() msgs := []proto.Message{ {ID: 1, Content: "A"}, {ID: 2, Content: "B"}, {ID: 15, Content: "C"}, {ID: 19, Content: "D"}, {ID: 20, Content: "E"}, } Convey("Partial response", t, func() { log := newMemLog() slice, err := log.Latest(ctx, 5, 0) So(err, ShouldBeNil) So(slice, ShouldNotBeNil) So(len(slice), ShouldEqual, 0) log.post(&msgs[0]) log.post(&msgs[1]) log.post(&msgs[2]) slice, err = log.Latest(ctx, 5, 0) So(err, ShouldBeNil) So(slice, ShouldResemble, msgs[:3]) }) Convey("Full response", t, func() { log := newMemLog() for _, msg := range msgs { posted := msg log.post(&posted) } slice, err := log.Latest(ctx, 3, 0) So(err, ShouldBeNil) So(slice, ShouldResemble, msgs[2:]) }) Convey("Before", t, func() { log := newMemLog() for _, msg := range msgs { posted := msg log.post(&posted) } slice, err := log.Latest(ctx, 3, 20) So(err, ShouldBeNil) So(slice, ShouldResemble, msgs[1:4]) }) }
func Run(args []string) { out = tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) if len(args) == 0 { generalHelp() return } exe := filepath.Base(os.Args[0]) cmd, ok := subcommands[args[0]] if !ok { fmt.Fprintf(os.Stderr, "%s: invalid command: %s\n", exe, args[0]) fmt.Fprintf(os.Stderr, "Run '%s help' for usage.\n", exe) os.Exit(2) } flags := cmd.flags() if err := flags.Parse(args[1:]); err != nil { fmt.Fprintf(os.Stderr, "%s %s: %s\n", exe, args[0], err) os.Exit(2) } ctx := logging.LoggingContext(scope.New(), os.Stdout, fmt.Sprintf("[%s] ", args[0])) logging.Logger(ctx).Printf("starting up") if err := cmd.run(ctx, flags.Args()); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } timeout := time.After(10 * time.Second) completed := make(chan struct{}) go func() { ctx.WaitGroup().Wait() close(completed) }() fmt.Println("waiting for graceful shutdown...") select { case <-timeout: fmt.Println("timed out") os.Exit(1) case <-completed: fmt.Println("ok") os.Exit(0) } }
func TestRunCommand(t *testing.T) { ctx := scope.New() Convey("Unregistered command prints error", t, func() { term := &testTerm{} runCommand(ctx, nil, "asdf", term, nil) So(term.String(), ShouldEqual, "invalid command: asdf\r\n") }) Convey("Registered command is invoked", t, func() { save := handlers defer func() { handlers = save }() handlers = map[string]handler{} register("test", testHandler{}) term := &testTerm{} runCommand(ctx, &Controller{}, "test", term, []string{"arg"}) So(term.String(), ShouldEqual, "ok\r\n") }) }
// NewBot creates a bot with the given configuration. It will create a bolt DB // if it does not already exist at the specified location. func NewBot(cfg BotConfig) (*Bot, error) { db, err := bolt.Open(cfg.DbPath, 0666, nil) if err != nil { return nil, err } ctx := scope.New() logger := logrus.New() logger.Level = logrus.DebugLevel cmd := make(chan interface{}) rooms := make(map[string]*Room) return &Bot{ Rooms: rooms, BotName: cfg.Name, ctx: ctx, DB: db, Logger: logger, cmd: cmd, }, nil }
// AddRoom adds a new Room to the bot with the given configuration. The context // for this room is distinct from the Bot's context. func (b *Bot) AddRoom(cfg RoomConfig) { b.Logger.Debugf("%s", len(cfg.AddlHandlers)) ctx := scope.New() logger := logrus.New() logger.Level = logrus.DebugLevel room := Room{ RoomName: cfg.RoomName, password: cfg.Password, Ctx: ctx, outbound: make(chan *proto.Packet, 5), inbound: make(chan *proto.Packet, 5), BotName: b.BotName, msgID: 0, Logger: logger, Handlers: cfg.AddlHandlers, DB: b.DB, conn: cfg.Conn, } b.Rooms[room.RoomName] = &room }
func (s *Server) handleRoomStatic(w http.ResponseWriter, r *http.Request) { ctx := scope.New() // Before creating an agent cookie, make this visitor look like a human. if err := r.ParseForm(); err != nil { s.serveErrorPage("bad request", http.StatusBadRequest, w, r) return } r.Form.Set("h", "1") // Tag the agent. client, cookie, _, err := getClient(ctx, s, r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if cookie != nil { w.Header().Add("Set-Cookie", cookie.String()) } // Parameterize and serve the page. prefix := mux.Vars(r)["prefix"] roomName := mux.Vars(r)["room"] room, err := s.resolveRoom(ctx, prefix, roomName, client) if err != nil { if err == proto.ErrRoomNotFound { if !s.allowRoomCreation || prefix != "" { s.serveErrorPage("room not found", http.StatusNotFound, w, r) return } } else { s.serveErrorPage(err.Error(), http.StatusInternalServerError, w, r) return } } params := map[string]interface{}{"RoomTitle": strings.TrimPrefix(room.Title(), "&")} s.servePage("room.html", params, w, r) }
func TestRoomBroadcast(t *testing.T) { userA := newSession("A", "A1", "ip1") userB := newSession("B", "B1", "ip2") userC := newSession("C", "C1", "ip3") ctx := scope.New() kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) roomp, err := NewRoom(ctx, kms, false, "test", "testver") if err != nil { t.Fatal(err) } room := roomp.(*memRoom) client := &proto.Client{Agent: &proto.Agent{}} client.FromRequest(ctx, &http.Request{}) Convey("Setup", t, func() { _, err := room.Join(ctx, userA) So(err, ShouldBeNil) _, err = room.Join(ctx, userB) So(err, ShouldBeNil) _, err = room.Join(ctx, userC) So(err, ShouldBeNil) }) Convey("Multiple exclude", t, func() { So(room.broadcast(ctx, proto.SendType, proto.Message{Content: "1"}, userA, userB), ShouldBeNil) So(userA.history, ShouldResemble, []message{ { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "B", IdentityView: proto.IdentityView{ID: "B"}, }, }, { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "C", IdentityView: proto.IdentityView{ID: "C"}, }, }, }) So(userB.history, ShouldResemble, []message{ { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "C", IdentityView: proto.IdentityView{ID: "C"}, }, }, }) So(userC.history, ShouldResemble, []message{{cmdType: proto.SendEventType, payload: proto.Message{Content: "1"}}}) }) Convey("No exclude", t, func() { So(room.broadcast(ctx, proto.SendType, proto.Message{Content: "2"}), ShouldBeNil) So(userA.history, ShouldResemble, []message{ { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "B", IdentityView: proto.IdentityView{ID: "B"}, }, }, { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "C", IdentityView: proto.IdentityView{ID: "C"}, }, }, { cmdType: proto.SendEventType, payload: proto.Message{Content: "2"}, }, }) So(userB.history, ShouldResemble, []message{ { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "C", IdentityView: proto.IdentityView{ID: "C"}, }, }, {cmdType: proto.SendEventType, payload: proto.Message{Content: "2"}}, }) So(userC.history, ShouldResemble, []message{ {cmdType: proto.SendEventType, payload: proto.Message{Content: "1"}}, {cmdType: proto.SendEventType, payload: proto.Message{Content: "2"}}, }) }) }
func TestDeleteMessage(t *testing.T) { ctx := scope.New() kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) session := mock.TestSession("test", "T1", "ip1") sendMessage := func(room proto.Room) (proto.Message, error) { msg := proto.Message{ Sender: proto.SessionView{ SessionID: "test", IdentityView: proto.IdentityView{ID: "test"}, }, Content: "test", } if managedRoom, ok := room.(proto.ManagedRoom); ok { key, err := managedRoom.MessageKey(ctx) if err != nil { return proto.Message{}, err } if key != nil { mkey := key.ManagedKey() if err := kms.DecryptKey(&mkey); err != nil { return proto.Message{}, err } if err := proto.EncryptMessage(&msg, key.KeyID(), &mkey); err != nil { return proto.Message{}, err } } } return room.Send(ctx, session, msg) } Convey("Delete message in public room", t, func() { ctrl := &Controller{ backend: &mock.TestBackend{}, kms: kms, } term := &testTerm{} public, err := ctrl.backend.CreateRoom(ctx, kms, false, "public") So(err, ShouldBeNil) sent, err := sendMessage(public) So(err, ShouldBeNil) runCommand(ctx, ctrl, "delete-message", term, []string{"public:" + sent.ID.String()}) deleted, err := public.GetMessage(ctx, sent.ID) So(err, ShouldBeNil) So(time.Time(deleted.Deleted).IsZero(), ShouldBeFalse) }) Convey("Delete message in private room", t, func() { ctrl := &Controller{ backend: &mock.TestBackend{}, kms: kms, } term := &testTerm{} private, err := ctrl.backend.CreateRoom(ctx, kms, true, "private") So(err, ShouldBeNil) runCommand(ctx, ctrl, "lock-room", term, []string{"private"}) sent, err := sendMessage(private) So(err, ShouldBeNil) runCommand(ctx, ctrl, "delete-message", term, []string{"private:" + sent.ID.String()}) deleted, err := private.GetMessage(ctx, sent.ID) So(err, ShouldBeNil) So(time.Time(deleted.Deleted).IsZero(), ShouldBeFalse) }) }
func TestGrants(t *testing.T) { Convey("Grant a capability on a room", t, func() { kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) ctx := scope.New() client := &proto.Client{Agent: &proto.Agent{}} client.FromRequest(ctx, &http.Request{}) backend := &mock.TestBackend{} room, err := backend.CreateRoom(ctx, kms, true, "test") So(err, ShouldBeNil) rkey, err := room.MessageKey(ctx) So(err, ShouldBeNil) mkey := rkey.ManagedKey() So(kms.DecryptKey(&mkey), ShouldBeNil) // Sign in as alice and send an encrypted message with aliceSendTime // as the nonce. aliceSendTime := time.Now() msgNonce := []byte(snowflake.NewFromTime(aliceSendTime).String()) aliceKey := &security.ManagedKey{ KeyType: security.AES256, Plaintext: make([]byte, security.AES256.KeySize()), } grant, err := security.GrantSharedSecretCapability(aliceKey, rkey.Nonce(), nil, mkey.Plaintext) So(err, ShouldBeNil) alice := mock.TestSession("Alice", "A1", "ip1") _, err = room.Join(ctx, alice) So(err, ShouldBeNil) msg := proto.Message{ ID: snowflake.NewFromTime(aliceSendTime), UnixTime: proto.Time(aliceSendTime), Content: "hello", } iv, err := base64.URLEncoding.DecodeString(grant.CapabilityID()) So(err, ShouldBeNil) payload := grant.EncryptedPayload() So(aliceKey.BlockCrypt(iv, aliceKey.Plaintext, payload, false), ShouldBeNil) key := &security.ManagedKey{ KeyType: security.AES128, } So(json.Unmarshal(aliceKey.Unpad(payload), &key.Plaintext), ShouldBeNil) digest, ciphertext, err := security.EncryptGCM( key, msgNonce, []byte(msg.Content), []byte("Alice")) So(err, ShouldBeNil) digestStr := base64.URLEncoding.EncodeToString(digest) cipherStr := base64.URLEncoding.EncodeToString(ciphertext) msg.Content = digestStr + "/" + cipherStr _, err = room.Send(ctx, alice, msg) So(err, ShouldBeNil) // Now sign in as bob and decrypt the message. bobKey := &security.ManagedKey{ KeyType: security.AES256, Plaintext: make([]byte, security.AES256.KeySize()), } //bobKey.Plaintext[0] = 1 grant, err = security.GrantSharedSecretCapability(bobKey, rkey.Nonce(), nil, mkey.Plaintext) So(err, ShouldBeNil) iv, err = base64.URLEncoding.DecodeString(grant.CapabilityID()) So(err, ShouldBeNil) payload = grant.EncryptedPayload() So(bobKey.BlockCrypt(iv, bobKey.Plaintext, payload, false), ShouldBeNil) key = &security.ManagedKey{ KeyType: security.AES128, } So(json.Unmarshal(bobKey.Unpad(payload), &key.Plaintext), ShouldBeNil) bob := mock.TestSession("Bob", "B1", "ip2") _, err = room.Join(ctx, bob) So(err, ShouldBeNil) log, err := room.Latest(ctx, 1, 0) So(err, ShouldBeNil) So(len(log), ShouldEqual, 1) msg = log[0] parts := strings.Split(msg.Content, "/") So(len(parts), ShouldEqual, 2) digest, err = base64.URLEncoding.DecodeString(parts[0]) So(err, ShouldBeNil) ciphertext, err = base64.URLEncoding.DecodeString(parts[1]) So(err, ShouldBeNil) plaintext, err := security.DecryptGCM(key, msgNonce, digest, ciphertext, []byte("Alice")) So(err, ShouldBeNil) So(string(plaintext), ShouldEqual, "hello") }) }