Skip to content

Commit

Permalink
add tea unittests for startup cases
Browse files Browse the repository at this point in the history
  • Loading branch information
adwski committed May 15, 2024
1 parent 58e8335 commit 1775d18
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 6 deletions.
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ require (
github.com/Eyevinn/dash-mpd v0.11.1
github.com/Eyevinn/mp4ff v0.44.0
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.26.1
github.com/charmbracelet/bubbletea v0.26.2
github.com/charmbracelet/huh v0.3.0
github.com/charmbracelet/lipgloss v0.10.0
github.com/charmbracelet/x/exp/teatest v0.0.0-20240514163027-fb066ab7c504
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/dgraph-io/ristretto v0.1.1
github.com/dustin/go-humanize v1.0.1
Expand All @@ -24,6 +25,7 @@ require (
github.com/json-iterator/go v1.1.12
github.com/labstack/echo-jwt/v4 v4.2.0
github.com/labstack/echo/v4 v4.12.0
github.com/labstack/gommon v0.4.2
github.com/minio/minio-go/v7 v7.0.70
github.com/minio/sha256-simd v1.0.1
github.com/redis/go-redis/v9 v9.5.1
Expand All @@ -43,10 +45,12 @@ require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymanbagabas/go-udiff v0.2.0 // indirect
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/exp/golden v0.0.0-20240222125807-0344fda748f8 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
Expand All @@ -66,7 +70,6 @@ require (
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
Expand Down
10 changes: 8 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0=
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
Expand All @@ -32,14 +34,18 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.26.1 h1:xujcQeF73rh4jwu3+zhfQsvV18x+7zIjlw7/CYbzGJ0=
github.com/charmbracelet/bubbletea v0.26.1/go.mod h1:FzKr7sKoO8iFVcdIBM9J0sJOcQv5nDQaYwsee3kpbgo=
github.com/charmbracelet/bubbletea v0.26.2 h1:Eeb+n75Om9gQ+I6YpbCXQRKHt5Pn4vMwusQpwLiEgJQ=
github.com/charmbracelet/bubbletea v0.26.2/go.mod h1:6I0nZ3YHUrQj7YHIHlM8RySX4ZIthTliMY+W8X8b+Gs=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE=
github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/charmbracelet/x/exp/golden v0.0.0-20240222125807-0344fda748f8 h1:kyT+aGp1z5jwlus3OY0cP6FuT05jYeeExx/4TYxnyrs=
github.com/charmbracelet/x/exp/golden v0.0.0-20240222125807-0344fda748f8/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/teatest v0.0.0-20240514163027-fb066ab7c504 h1:xW0KK1W/5pk3LCg8cGl2GEhcsPp3CPyx7rtyiyxRGj8=
github.com/charmbracelet/x/exp/teatest v0.0.0-20240514163027-fb066ab7c504/go.mod h1:hj+TFTT7V4sVYggNy1SkYXgE2ZWscgeteB8FqgqCmWk=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
9 changes: 7 additions & 2 deletions internal/tool/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ func New() (*Tool, error) {

// Run starts tool. It returns only on interrupt.
func (t *Tool) Run() int {
return t.RunWithContext(context.Background())
}

func (t *Tool) RunWithContext(ctx context.Context) int {
var code int
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
wg := &sync.WaitGroup{}
wg.Add(1)
Expand All @@ -98,7 +102,7 @@ func (t *Tool) Run() int {
func (t *Tool) run(ctx context.Context, wg *sync.WaitGroup) error {
defer wg.Done()
t.initialize()
t.prog = tea.NewProgram(t)
t.prog = tea.NewProgram(t, tea.WithContext(ctx))
wg.Add(1)
go t.listenForEvents(ctx, wg)
if _, err := t.prog.Run(); err != nil {
Expand Down Expand Up @@ -250,6 +254,7 @@ func (t *Tool) cycleViews() {
switch {
case t.err != nil:
// in case of error, show it to user
t.logger.Debug("error occurred, switching to err screen", zap.Error(t.err))
t.screen = newErrorScreen(t.err)
case t.state.noEndpoint(), t.noClients():
// invalid endpoint, should configure it again
Expand Down
181 changes: 181 additions & 0 deletions internal/tool/tool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//nolint:lll // long config samples
package tool

import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/exp/teatest"
"github.com/stretchr/testify/require"
)

func TestTool_StartNoEndpointFailedConnect(t *testing.T) {
tool, err := New()
require.NoError(t, err)

tool.dir = t.TempDir()
tool.initialize()
require.NoError(t, tool.err)

tm := teatest.NewTestModel(t, tool, teatest.WithInitialTermSize(300, 100))
teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("Configure ViDi endpoint URL"))
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3))

tm.Send(tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune("http://127.0.0.1:12345"),
})
tm.Send(tea.KeyMsg{
Type: tea.KeyEnter,
})

teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("cannot contact ViDi endpoint"))
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3))

tm.Send(struct{}{})
tm.Send(tea.KeyMsg{
Type: tea.KeyEnter,
})

teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("Configure ViDi endpoint URL"))
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3))

tm.Send(tea.KeyMsg{
Type: tea.KeyCtrlC,
})
}

const validRemoveConfig = `{
"user_api_url": "http://localhost/api/users",
"video_api_url": "localhost:443",
"vidi_ca": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZoRENDQTJ5Z0F3SUJBZ0lVWTNSZmo0V1I3VkNwUEREZ2liR1Q0V1RySzlBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd09qRUxNQWtHQTFVRUJoTUNVbFV4RFRBTEJnTlZCQW9NQkZacFJHa3hEVEFMQmdOVkJBc01CSFpwWkdreApEVEFMQmdOVkJBTU1CSFpwWkdrd0hoY05NalF3TlRFeE1qRXhOekEzV2hjTk1qVXdOVEV4TWpFeE56QTNXakE2Ck1Rc3dDUVlEVlFRR0V3SlNWVEVOTUFzR0ExVUVDZ3dFVm1sRWFURU5NQXNHQTFVRUN3d0VkbWxrYVRFTk1Bc0cKQTFVRUF3d0VkbWxrYVRDQ0FpSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTGFOdjVPSgp6WmpYWGZ4enhaYWI3L2FJMjZkMWc4TEp1Y0R5SnpQY2JvUDdCSnh5RHFsUXAxT0RGZ2N0S2JmUWY4QlJnZG1oCjBKVFdJd0dyaWNwN2FJOEprd2lvQXZWRmZaOTNtTElCQUc1Zjc5RVp3Y0YyZnllSjNQOG5WdU5FRDVSclR2Z3EKaUNidU5EdkJQMG5uUEhWeDhDYnU1WFpyaGJVV2xZTjkvQW1kb0dFRTJXSlpVb2NJdGxKQnF3bmkzWVpaekxKZQpPZFV6Z00rY2loYjljV3N5OXBvMkZ0V0Z6YnRySnNCaXhLRElUNk9IYTFLTHdoS0RSUFJLSnNmNTNSQ3EvVUlJCkRVM2I1WGx1UFFvQVJvaHl0bzhYdDNDYkgxYWJtRStneUN4QzR6czVsL2dlRVU5N2FQKzRJQ1JlVGFMemdFckUKN21Nakd2NWFQTTFxYVc2MFZyaUwxMUs4ajlwMGpJeDdkRnBpeVZ5S1g1allCVG8zWXBKdTZIc2lldS92MEZyNQo4czdDcmQ2NlFxNmRXR2lpdlV3VXhlNVF5WVJYdkZxQ2hEOERvRHJadGE0UXpQaXhMcldaSjNhU0UwS1JJYyszCi8weE4rYVZhMXQwU0pNMXNJMkMyRVd5ZmtMVWNqcUpLaDNaY0RidHBsUFQ4OXEwV1VEOUVJVXNlU0xtVER4QXIKMURLaEdKaWxhcjUweG1GOVFxYkgrTHFNWWl6V1lPVTBqRUtlSWpsN1RuT2xXSy9WNC9BMDBvL0NnamNCcnVyRgprVS92bTNFbGpYeXNmMlh5UVFmbFdISExVN3hWSElUYmhucGFObXFDaENnQVRFTzhUemlpem1EMEp3TkFRTld1CmdpV3Q0SEVZbDJUdEFRdXdaaEJTNUdmbllBMld2VlQ2ZEs4MUFnTUJBQUdqZ1lFd2Z6QWRCZ05WSFE0RUZnUVUKTmQ5WWlHa0RZVlB2T2JNOEx6UGFOQjY5aDZvd0h3WURWUjBqQkJnd0ZvQVVOZDlZaUdrRFlWUHZPYk04THpQYQpOQjY5aDZvd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBc0JnTlZIUkVFSlRBamdnbHNiMk5oYkdodmMzU0hCSDhBCkFBR0hFQUFBQUFBQUFBQUFBQUFBQUFBQUFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBSHZOY2lReDd5dWoKUlBqelk0Y2c0dGtKbkNoZjFGcHJWbThYbC9GL1FteC9KK0JOWjNyTm5ZdWVFN1ZXSUV5bzdRQ3Y4NjI4TTF4MwpnRy9iODRSOFJ6ajhWeXhXZWpCdnZpTGNIZW5VZUJTbnVmS2JOUlBnRmFlSWk1L1k3akQ3Qk9vVDAzdzJUUGZYCmZUbG5IRTE5ZndvaDFuUFFRZXJEazhuSFVnZmdhUHIzWXNOWkdPL0MrbmFtcytoTGp4R0dEcWJPa1psQW8wU2YKaCtxQ3h4dVdwT3VJODBLajNOVVc5L0kwTERtTFRzeHBXcGxmT25ibm9RbmUyYXZLdEtadkxHa05vVDFZMTd0bQpSRFRERGhtYTBaTytTaTk0dlF6ZXhWUUg0QnBOd3QydDZnTVRHTW0yRTRBS0FFc2FXWHZ0Y0dPaGhudjJ5NzkyCjJUa3A3aEZmZEFjWlk2bDVpZjZHZGxtU1gwdUVFcWV4ZjRuNjJvaHF1bVp3aVZldHVEUndaMitnSEFwdGwyeG0KekNOUHZOSEY3emxpRGo2VE9OWXoxV0w0NGViNldGbTV6YThlTU4xNFNwd01mUTBRUTV4MjUwOCttNW4yM2JJZApmaUVnVytORTUzLzV5c2wyeWpVSGtiUWhpY1hqZVlsei9sNk9jRVFDN2pGaHVwcmZaWmR2Yzd1QWFJczBINER2CjdYOXdvRlh5OXNzUUwwZk1nemdIQ2M5VmQ4TDlabWNWazBhV3RncWxiRERYM2wreVFHUjBnNW5pcnhDUGZZNC8KWEF2R2p4ZFllcnBxNGFmNkNIeHRCZkdMaGszZnYwbmM4REtDRnZUZUhHTC9SbmkzZnB3SURHN2dTcUZaOFBtVQpTQStKN0Y0UUlNaVNrLy9JYUxlWThBdnRDUG1RY3VsZgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
}`

func TestTool_StartNoUsers(t *testing.T) {
tool, err := New()
require.NoError(t, err)

tool.dir = t.TempDir()
tool.initialize()
require.NoError(t, tool.err)

tm := teatest.NewTestModel(t, tool, teatest.WithInitialTermSize(300, 100))

teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("Configure ViDi endpoint URL"))
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3))

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, errW := w.Write([]byte(validRemoveConfig))
require.NoError(t, errW)
}))
defer srv.Close()

tm.Send(tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune(srv.URL),
})
tm.Send(tea.KeyMsg{
Type: tea.KeyEnter,
})

teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("No locally stored users have found"))
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3))
}

const toolStateValidActiveUser = `{
"endpoint": "%s",
"users": [
{
"name": "user123",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJleHAiOjQwODI0Njc4Mzl9.TQy6X7dVkSRf92XjN-tRI9-fQjOOml6vcJn3Qb5iNt8"
}
],
"current_user": 0
}`

func TestTool_StartValidActiveUser(t *testing.T) {
tool, err := New()
require.NoError(t, err)

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, errW := w.Write([]byte(validRemoveConfig))
require.NoError(t, errW)
}))
defer srv.Close()

tool.dir = t.TempDir()
err = os.WriteFile(tool.dir+stateFile, []byte(fmt.Sprintf(toolStateValidActiveUser, srv.URL)), 0600)
require.NoError(t, err)

tool.initialize()
require.NoError(t, tool.err)

tm := teatest.NewTestModel(t, tool, teatest.WithInitialTermSize(300, 100))

teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte(greetMessageTxt))
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3))
}

const toolStateValidActiveUserExpiredToken = `{
"endpoint": "%s",
"users": [
{
"name": "testUser",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJQQXg3b0RZOVJQZVJVS09FSG1GQXpnIiwibmFtZSI6InVzZXIxMjMiLCJleHAiOjE3MTU1NjI4MjV9.SdMNc0Pf5EWWf5SwPjVpy8PtLbnFT0U-XzoD7-Ayg6Y"
}
],
"current_user": 0
}`

func TestTool_StartValidActiveUserExpiredToken(t *testing.T) {
tool, err := New()
require.NoError(t, err)

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, errW := w.Write([]byte(validRemoveConfig))
require.NoError(t, errW)
}))
defer srv.Close()

tool.dir = t.TempDir()
err = os.WriteFile(tool.dir+stateFile, []byte(fmt.Sprintf(toolStateValidActiveUserExpiredToken, srv.URL)), 0600)
require.NoError(t, err)

tool.initialize()
require.NoError(t, tool.err)

tm := teatest.NewTestModel(t, tool, teatest.WithInitialTermSize(300, 100))

teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("Enter your password again or select another user")) &&
bytes.Contains(bts, []byte("Enter password for 'testUser'")) &&
bytes.Contains(bts, []byte("Login with another user"))
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3))

tm.Send(tea.KeyMsg{
Type: tea.KeyEnter,
})

teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("Enter password for 'testUser'")) &&
!bytes.Contains(bts, []byte("Login with another user"))
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3))
}

0 comments on commit 1775d18

Please sign in to comment.