ACN state handling & tests
This commit is contained in:
parent
c453bd831c
commit
ad640636c3
27
acn.go
27
acn.go
|
@ -35,17 +35,6 @@ func isConnected(m model) (bool, tea.Msg) {
|
|||
}
|
||||
|
||||
func turnAcnOn(m *model) tea.Msg {
|
||||
if ok, msg := isConnected(*m); !ok {
|
||||
switch msg.(type) {
|
||||
case acnConnectingMsg:
|
||||
return msg
|
||||
case acnAlreadyOnlineMsg:
|
||||
return msg
|
||||
}
|
||||
}
|
||||
|
||||
m.acnState = connecting
|
||||
|
||||
mrand.New(mrand.NewSource(int64(time.Now().Nanosecond())))
|
||||
port := mrand.Intn(1000) + 9600
|
||||
controlPort := port + 1
|
||||
|
@ -113,22 +102,10 @@ func turnAcnOn(m *model) tea.Msg {
|
|||
}
|
||||
|
||||
func turnAcnOff(m *model, quitProgram bool) tea.Msg {
|
||||
switch m.acnState {
|
||||
case offline:
|
||||
if !quitProgram {
|
||||
return acnOfflineMsg{}
|
||||
}
|
||||
case connecting:
|
||||
return acnConnectingMsg{}
|
||||
}
|
||||
|
||||
if m.app != nil {
|
||||
if m.acnState != offline {
|
||||
logToFile(*m, "Shutting down application and ACN now")
|
||||
m.app.Shutdown()
|
||||
}
|
||||
|
||||
if m.acn != nil {
|
||||
m.acn.Close()
|
||||
}
|
||||
|
||||
return acnOffMsg{quitProgram: quitProgram}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import "log"
|
||||
|
||||
// LogToFile logs a message to a log file.
|
||||
func logToFile(m model, msg string) {
|
||||
if m.debug {
|
||||
log.Printf("debug: %s", msg)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/user"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
openPrivacyLog "git.openprivacy.ca/openprivacy/log"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/x/exp/teatest"
|
||||
)
|
||||
|
||||
const (
|
||||
cairdeTestLogFile = "cairde-test.log"
|
||||
cwtchTestLogFile = "cwtch-test.log"
|
||||
)
|
||||
|
||||
var currentUser *user.User
|
||||
var cairdeLogHandler *os.File
|
||||
var cwtchLogHandler *openPrivacyLog.Logger
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
defer setupAndTeardown()()
|
||||
m.Run()
|
||||
}
|
||||
|
||||
func setupAndTeardown() func() {
|
||||
_, err := tea.LogToFile(cairdeTestLogFile, "debug")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cairdeLogHandler, err = os.OpenFile(cairdeTestLogFile, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cwtchLogHandler, err = openPrivacyLog.NewFile(openPrivacyLog.LevelDebug, cwtchTestLogFile)
|
||||
if err == nil {
|
||||
openPrivacyLog.SetStd(cwtchLogHandler)
|
||||
}
|
||||
|
||||
currentUser, err = user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return func() {
|
||||
if err := cairdeLogHandler.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := os.Remove(cairdeTestLogFile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := os.Remove(cwtchTestLogFile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnOff(t *testing.T) {
|
||||
m := newModel(currentUser.Username, currentUser.HomeDir, true)
|
||||
testModel := teatest.NewTestModel(t, m)
|
||||
|
||||
testModel.Send(tea.KeyMsg{
|
||||
Type: tea.KeyRunes,
|
||||
Runes: []rune("ctrl+c"),
|
||||
})
|
||||
|
||||
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
|
||||
}
|
||||
|
||||
func TestAcnOnOff(t *testing.T) {
|
||||
m := newModel(currentUser.Username, currentUser.HomeDir, true)
|
||||
testModel := teatest.NewTestModel(t, m)
|
||||
|
||||
testModel.Type("/acn on")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/acn off")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN successfully turned off"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*3),
|
||||
)
|
||||
|
||||
testModel.Send(tea.KeyMsg{
|
||||
Type: tea.KeyRunes,
|
||||
Runes: []rune("ctrl+c"),
|
||||
})
|
||||
|
||||
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
|
||||
}
|
||||
|
||||
func TestAcnOnThenQuit(t *testing.T) {
|
||||
m := newModel(currentUser.Username, currentUser.HomeDir, true)
|
||||
testModel := teatest.NewTestModel(t, m)
|
||||
|
||||
testModel.Type("/acn on")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/quit")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("Shutting down application and ACN now"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*3),
|
||||
)
|
||||
|
||||
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
|
||||
}
|
||||
|
||||
func TestAcnOnThenOnAsksToHold(t *testing.T) {
|
||||
m := newModel(currentUser.Username, currentUser.HomeDir, true)
|
||||
testModel := teatest.NewTestModel(t, m)
|
||||
|
||||
testModel.Type("/acn on")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("Initialising Tor ACN"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/acn on")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN still initialising, please hold"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*3),
|
||||
)
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/quit")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("Shutting down application and ACN now"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*3),
|
||||
)
|
||||
|
||||
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
|
||||
}
|
||||
|
||||
func TestOnThenOffAsksToWait(t *testing.T) {
|
||||
m := newModel(currentUser.Username, currentUser.HomeDir, true)
|
||||
testModel := teatest.NewTestModel(t, m)
|
||||
|
||||
testModel.Type("/acn on")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("Initialising Tor ACN..."))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/acn on")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN still initialising, please hold"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*3),
|
||||
)
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/quit")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("Shutting down application and ACN now"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*3),
|
||||
)
|
||||
|
||||
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
|
||||
}
|
||||
|
||||
func TestAcnOnThenQuitAsksToWait(t *testing.T) {
|
||||
m := newModel(currentUser.Username, currentUser.HomeDir, true)
|
||||
testModel := teatest.NewTestModel(t, m)
|
||||
|
||||
testModel.Type("/acn on")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("Initialising Tor ACN..."))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/quit")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN still initialising, please hold"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*3),
|
||||
)
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN is up and running, all engines go"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/quit")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("Shutting down application and ACN now"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*3),
|
||||
)
|
||||
|
||||
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
|
||||
}
|
||||
|
||||
func TestAcnOffIsOff(t *testing.T) {
|
||||
m := newModel(currentUser.Username, currentUser.HomeDir, true)
|
||||
testModel := teatest.NewTestModel(t, m)
|
||||
|
||||
testModel.Type("/acn off")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
teatest.WaitFor(
|
||||
t, cairdeLogHandler,
|
||||
func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("ACN is currently offline"))
|
||||
},
|
||||
teatest.WithCheckInterval(time.Millisecond*100),
|
||||
teatest.WithDuration(time.Second*10),
|
||||
)
|
||||
|
||||
testModel.Type("/quit")
|
||||
testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
|
||||
testModel.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
|
||||
}
|
27
model.go
27
model.go
|
@ -133,6 +133,7 @@ func (m model) sendStatusCmd(lines ...string) tea.Cmd {
|
|||
// we want to render messages to the status buffer but still have work to do.
|
||||
func (m model) sendStatus(lines ...string) {
|
||||
for _, line := range lines {
|
||||
logToFile(m, line)
|
||||
m.statusBuffer <- line
|
||||
}
|
||||
}
|
||||
|
@ -263,6 +264,17 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
cmds = append(cmds, m.sendStatusCmd(msg.output...))
|
||||
|
||||
case turnAcnOffMsg:
|
||||
switch m.acnState {
|
||||
case offline:
|
||||
if !msg.quitProgram {
|
||||
cmds = append(cmds, func() tea.Msg { return acnOfflineMsg{} })
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
case connecting:
|
||||
cmds = append(cmds, func() tea.Msg { return acnConnectingMsg{} })
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
m.input.Reset()
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return turnAcnOff(&m, msg.quitProgram)
|
||||
|
@ -276,9 +288,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.acnState = offline
|
||||
|
||||
case turnAcnOnMsg:
|
||||
// TODO: stop if we're already connecting!
|
||||
// state management is getting a little tricky :/
|
||||
switch m.acnState {
|
||||
case connecting:
|
||||
cmds = append(cmds, func() tea.Msg { return acnConnectingMsg{} })
|
||||
return m, tea.Batch(cmds...)
|
||||
case connected:
|
||||
cmds = append(cmds, func() tea.Msg { return acnAlreadyOnlineMsg{} })
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
m.acnState = connecting
|
||||
|
||||
m.input.Reset()
|
||||
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return turnAcnOn(&m)
|
||||
})
|
||||
|
@ -390,6 +412,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case sendStatusMsg:
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
for _, line := range msg.lines {
|
||||
logToFile(m, line)
|
||||
m.statusBuffer <- line
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/x/exp/teatest"
|
||||
)
|
||||
|
||||
func TestCairdeOnOffWorks(t *testing.T) {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m := newModel(user.Username, user.HomeDir, false)
|
||||
tm := teatest.NewTestModel(t, m)
|
||||
|
||||
tm.Send(tea.KeyMsg{
|
||||
Type: tea.KeyRunes,
|
||||
Runes: []rune("ctrl+c"),
|
||||
})
|
||||
|
||||
tm.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
|
||||
}
|
Loading…
Reference in New Issue