2022-01-09 20:00:53 +01:00
package main
import (
"bytes"
2022-04-07 08:17:58 +02:00
"encoding/json"
2022-04-07 07:44:10 +02:00
"fmt"
2022-04-07 08:17:58 +02:00
"net/http"
2022-04-07 07:43:07 +02:00
"os"
2022-04-07 07:44:10 +02:00
"os/exec"
2022-04-16 09:56:08 +02:00
"path"
2022-01-09 23:34:59 +01:00
"regexp"
2022-04-12 07:36:52 +02:00
"runtime"
2022-01-09 23:34:59 +01:00
"strings"
2022-01-09 20:00:53 +01:00
"testing"
2022-04-15 05:18:49 +02:00
"time"
"github.com/google/go-cmp/cmp"
2022-01-09 20:00:53 +01:00
2022-04-08 05:59:40 +02:00
"github.com/ddworken/hishtory/client/data"
2022-05-02 04:37:26 +02:00
"github.com/ddworken/hishtory/client/lib"
2022-01-09 20:00:53 +01:00
"github.com/ddworken/hishtory/shared"
)
2022-04-15 20:20:23 +02:00
func TestMain ( m * testing . M ) {
defer shared . RunTestServer ( ) ( )
2022-04-16 03:12:26 +02:00
cmd := exec . Command ( "go" , "build" , "-o" , "/tmp/client" )
err := cmd . Run ( )
if err != nil {
panic ( fmt . Sprintf ( "failed to build client: %v" , err ) )
}
2022-04-15 20:20:23 +02:00
m . Run ( )
}
2022-04-18 04:54:17 +02:00
type shellTester interface {
RunInteractiveShell ( t * testing . T , script string ) string
RunInteractiveShellRelaxed ( t * testing . T , script string ) ( string , error )
ShellName ( ) string
}
type bashTester struct {
shellTester
}
func ( b bashTester ) RunInteractiveShell ( t * testing . T , script string ) string {
out , err := b . RunInteractiveShellRelaxed ( t , "set -emo pipefail\n" + script )
2022-04-08 08:25:13 +02:00
if err != nil {
2022-04-12 07:36:52 +02:00
_ , filename , line , _ := runtime . Caller ( 1 )
t . Fatalf ( "error when running command at %s:%d: %v" , filename , line , err )
2022-04-08 08:25:13 +02:00
}
return out
}
2022-04-18 04:54:17 +02:00
func ( b bashTester ) RunInteractiveShellRelaxed ( t * testing . T , script string ) ( string , error ) {
2022-01-09 23:34:59 +01:00
cmd := exec . Command ( "bash" , "-i" )
cmd . Stdin = strings . NewReader ( script )
2022-04-08 08:25:13 +02:00
var stdout bytes . Buffer
cmd . Stdout = & stdout
var stderr bytes . Buffer
cmd . Stderr = & stderr
err := cmd . Run ( )
if err != nil {
return "" , fmt . Errorf ( "unexpected error when running commands, out=%#v, err=%#v: %v" , stdout . String ( ) , stderr . String ( ) , err )
}
outStr := stdout . String ( )
2022-04-15 05:18:49 +02:00
if strings . Contains ( outStr , "hishtory fatal error" ) {
2022-04-07 08:05:30 +02:00
t . Fatalf ( "Ran command, but hishtory had a fatal error! out=%#v" , outStr )
}
2022-04-08 08:25:13 +02:00
return outStr , nil
2022-01-09 23:34:59 +01:00
}
2022-04-18 04:54:17 +02:00
func ( b bashTester ) ShellName ( ) string {
return "bash"
}
type zshTester struct {
shellTester
}
func ( z zshTester ) RunInteractiveShell ( t * testing . T , script string ) string {
// TODO: make this strict
res , err := z . RunInteractiveShellRelaxed ( t , script )
if err != nil {
t . Fatal ( err )
}
return res
}
func ( z zshTester ) RunInteractiveShellRelaxed ( t * testing . T , script string ) ( string , error ) {
cmd := exec . Command ( "zsh" , "-is" )
cmd . Stdin = strings . NewReader ( script )
var stdout bytes . Buffer
cmd . Stdout = & stdout
var stderr bytes . Buffer
cmd . Stderr = & stderr
err := cmd . Run ( )
if err != nil {
return "" , fmt . Errorf ( "unexpected error when running commands, out=%#v, err=%#v: %v" , stdout . String ( ) , stderr . String ( ) , err )
}
outStr := stdout . String ( )
if strings . Contains ( outStr , "hishtory fatal error" ) {
t . Fatalf ( "Ran command, but hishtory had a fatal error! out=%#v" , outStr )
}
return outStr , nil
}
func ( z zshTester ) ShellName ( ) string {
return "zsh"
}
var shellTesters [ ] shellTester = [ ] shellTester { bashTester { } , zshTester { } }
func TestParameterized ( t * testing . T ) {
for _ , tester := range shellTesters {
t . Run ( "testRepeatedCommandThenQuery/" + tester . ShellName ( ) , func ( t * testing . T ) { testRepeatedCommandThenQuery ( t , tester ) } )
t . Run ( "testRepeatedCommandAndQuery/" + tester . ShellName ( ) , func ( t * testing . T ) { testRepeatedCommandAndQuery ( t , tester ) } )
t . Run ( "testRepeatedEnableDisable/" + tester . ShellName ( ) , func ( t * testing . T ) { testRepeatedEnableDisable ( t , tester ) } )
t . Run ( "testExcludeHiddenCommand/" + tester . ShellName ( ) , func ( t * testing . T ) { testExcludeHiddenCommand ( t , tester ) } )
t . Run ( "testUpdate/" + tester . ShellName ( ) , func ( t * testing . T ) { testUpdate ( t , tester ) } )
t . Run ( "testAdvancedQuery/" + tester . ShellName ( ) , func ( t * testing . T ) { testAdvancedQuery ( t , tester ) } )
t . Run ( "testIntegration/" + tester . ShellName ( ) , func ( t * testing . T ) { testIntegration ( t , tester ) } )
t . Run ( "testIntegrationWithNewDevice/" + tester . ShellName ( ) , func ( t * testing . T ) { testIntegrationWithNewDevice ( t , tester ) } )
t . Run ( "testHishtoryBackgroundSaving/" + tester . ShellName ( ) , func ( t * testing . T ) { testHishtoryBackgroundSaving ( t , tester ) } )
t . Run ( "testDisplayTable/" + tester . ShellName ( ) , func ( t * testing . T ) { testDisplayTable ( t , tester ) } )
2022-04-19 08:07:39 +02:00
t . Run ( "testTableDisplayCwd/" + tester . ShellName ( ) , func ( t * testing . T ) { testTableDisplayCwd ( t , tester ) } )
2022-04-19 07:07:10 +02:00
t . Run ( "testTimestampsAreReasonablyCorrect/" + tester . ShellName ( ) , func ( t * testing . T ) { testTimestampsAreReasonablyCorrect ( t , tester ) } )
2022-05-02 04:37:26 +02:00
t . Run ( "testRequestAndReceiveDbDump/" + tester . ShellName ( ) , func ( t * testing . T ) { testRequestAndReceiveDbDump ( t , tester ) } )
2022-04-18 04:54:17 +02:00
}
}
func testIntegration ( t * testing . T , tester shellTester ) {
2022-01-09 20:00:53 +01:00
// Set up
2022-03-30 06:56:28 +02:00
defer shared . BackupAndRestore ( t ) ( )
2022-04-07 03:18:46 +02:00
// Run the test
2022-04-18 04:54:17 +02:00
testBasicUserFlow ( t , tester )
2022-04-06 08:31:24 +02:00
}
2022-01-09 20:00:53 +01:00
2022-04-18 04:54:17 +02:00
func testIntegrationWithNewDevice ( t * testing . T , tester shellTester ) {
2022-04-06 08:31:24 +02:00
// Set up
defer shared . BackupAndRestore ( t ) ( )
2022-04-07 03:18:46 +02:00
// Run the test
2022-04-18 04:54:17 +02:00
userSecret := testBasicUserFlow ( t , tester )
2022-04-06 08:31:24 +02:00
// Clear all local state
shared . ResetLocalState ( t )
// Install it again
2022-04-18 04:54:17 +02:00
installHishtory ( t , tester , userSecret )
2022-01-09 23:34:59 +01:00
2022-04-07 03:18:46 +02:00
// Querying should show the history from the previous run
2022-04-18 04:54:17 +02:00
out := hishtoryQuery ( t , tester , "" )
2022-04-06 08:31:24 +02:00
expected := [ ] string { "echo thisisrecorded" , "hishtory enable" , "echo bar" , "echo foo" , "ls /foo" , "ls /bar" , "ls /a" }
for _ , item := range expected {
if ! strings . Contains ( out , item ) {
t . Fatalf ( "output is missing expected item %#v: %#v" , item , out )
}
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
}
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , "echo mynewcommand" )
out = hishtoryQuery ( t , tester , "" )
2022-04-06 08:31:24 +02:00
if ! strings . Contains ( out , "echo mynewcommand" ) {
t . Fatalf ( "output is missing `echo mynewcommand`" )
}
if strings . Count ( out , "echo mynewcommand" ) != 1 {
t . Fatalf ( "output has `echo mynewcommand` the wrong number of times" )
}
2022-04-07 03:18:46 +02:00
// Clear local state again
2022-04-07 02:47:21 +02:00
shared . ResetLocalState ( t )
// Install it a 3rd time
2022-04-18 04:54:17 +02:00
installHishtory ( t , tester , "adifferentsecret" )
2022-04-07 02:47:21 +02:00
2022-04-07 07:43:07 +02:00
// Run a command that shouldn't be in the hishtory later on
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , ` echo notinthehistory ` )
out = hishtoryQuery ( t , tester , "" )
2022-04-07 07:43:07 +02:00
if ! strings . Contains ( out , "echo notinthehistory" ) {
t . Fatalf ( "output is missing `echo notinthehistory`" )
}
2022-04-07 03:18:46 +02:00
// Set the secret key to the previous secret key
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory init ` + userSecret )
2022-04-07 03:18:46 +02:00
if ! strings . Contains ( out , "Setting secret hishtory key to " + userSecret ) {
2022-04-07 02:47:21 +02:00
t . Fatalf ( "Failed to re-init with the user secret: %v" , out )
}
2022-04-07 03:18:46 +02:00
// Querying should show the history from the previous run
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-04-07 02:47:21 +02:00
expected = [ ] string { "echo thisisrecorded" , "echo mynewcommand" , "hishtory enable" , "echo bar" , "echo foo" , "ls /foo" , "ls /bar" , "ls /a" }
for _ , item := range expected {
if ! strings . Contains ( out , item ) {
t . Fatalf ( "output is missing expected item %#v: %#v" , item , out )
}
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
}
2022-04-07 07:43:07 +02:00
// But not from the previous account
if strings . Contains ( out , "notinthehistory" ) {
t . Fatalf ( "output contains the unexpected item: notinthehistory" )
}
2022-04-07 02:47:21 +02:00
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , "echo mynewercommand" )
out = hishtoryQuery ( t , tester , "" )
2022-04-07 02:47:21 +02:00
if ! strings . Contains ( out , "echo mynewercommand" ) {
t . Fatalf ( "output is missing `echo mynewercommand`" )
}
if strings . Count ( out , "echo mynewercommand" ) != 1 {
t . Fatalf ( "output has `echo mynewercommand` the wrong number of times" )
}
2022-04-06 08:31:24 +02:00
2022-04-07 07:49:45 +02:00
// Manually submit an event that isn't in the local DB, and then we'll
2022-04-07 08:17:58 +02:00
// check if we see it when we do a query without ever having done an init
2022-04-08 05:59:40 +02:00
newEntry := data . MakeFakeHistoryEntry ( "othercomputer" )
2022-04-11 02:38:20 +02:00
manuallySubmitHistoryEntry ( t , userSecret , newEntry )
2022-04-07 08:17:58 +02:00
// Now check if that is in there when we do hishtory query
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-04-07 07:49:45 +02:00
if ! strings . Contains ( out , "othercomputer" ) {
t . Fatalf ( "hishtory query doesn't contain cmd run on another machine! out=%#v" , out )
}
2022-04-07 08:05:30 +02:00
// Finally, test the export command
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail | grep -v '/tmp/client install' ` )
2022-04-15 05:18:49 +02:00
if strings . Contains ( out , "thisisnotrecorded" ) {
t . Fatalf ( "hishtory export contains a command that should not have been recorded, out=%#v" , out )
}
2022-04-20 06:05:54 +02:00
expectedOutputWithoutKey := "hishtory status\nhishtory query\nls /a\nls /bar\nls /foo\necho foo\necho bar\nhishtory enable\necho thisisrecorded\nhishtory query\nhishtory query foo\necho hello | grep complex | sed s/h/i/g; echo baz && echo \"fo 'o\"\nhishtory query complex\nhishtory query\necho mynewcommand\nhishtory query\nhishtory init %s\nhishtory query\necho mynewercommand\nhishtory query\nothercomputer\nhishtory query\n"
2022-04-15 05:18:49 +02:00
expectedOutput := fmt . Sprintf ( expectedOutputWithoutKey , userSecret )
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
2022-04-16 07:54:38 +02:00
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
2022-04-07 08:05:30 +02:00
}
2022-04-06 08:31:24 +02:00
}
2022-04-18 04:54:17 +02:00
func installHishtory ( t * testing . T , tester shellTester , userSecret string ) string {
out := tester . RunInteractiveShell ( t , ` /tmp/client install ` + userSecret )
2022-04-06 08:31:24 +02:00
r := regexp . MustCompile ( ` Setting secret hishtory key to (.*) ` )
matches := r . FindStringSubmatch ( out )
if len ( matches ) != 2 {
t . Fatalf ( "Failed to extract userSecret from output: matches=%#v" , matches )
}
2022-04-08 07:53:39 +02:00
return matches [ 1 ]
}
2022-04-18 04:54:17 +02:00
func testBasicUserFlow ( t * testing . T , tester shellTester ) string {
2022-04-08 07:53:39 +02:00
// Test install
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-06 08:31:24 +02:00
2022-04-07 07:43:07 +02:00
// Test the status subcommand
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-04-09 23:37:21 +02:00
if out != fmt . Sprintf ( "Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
2022-04-07 02:47:21 +02:00
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
2022-04-16 09:56:08 +02:00
// Assert that hishtory is correctly using the dev config.sh
homedir , err := os . UserHomeDir ( )
if err != nil {
t . Fatalf ( "failed to get homedir: %v" , err )
}
dat , err := os . ReadFile ( path . Join ( homedir , shared . HISHTORY_PATH , "config.sh" ) )
if err != nil {
t . Fatalf ( "failed to read config.sh: %v" , err )
}
if ! strings . Contains ( string ( dat ) , "except it doesn't run the save process in the background" ) {
t . Fatalf ( "config.sh is the prod version when it shouldn't be, config.sh=%#v" , dat )
}
2022-04-07 07:44:10 +02:00
// Test the banner
2022-04-07 07:43:07 +02:00
os . Setenv ( "FORCED_BANNER" , "HELLO_FROM_SERVER" )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-04-16 09:44:47 +02:00
if ! strings . Contains ( out , "HELLO_FROM_SERVER\nHostname" ) {
2022-04-07 07:43:07 +02:00
t . Fatalf ( "hishtory query didn't show the banner message! out=%#v" , out )
}
os . Setenv ( "FORCED_BANNER" , "" )
2022-01-09 23:34:59 +01:00
// Test recording commands
2022-04-20 06:05:54 +02:00
out , err = tester . RunInteractiveShellRelaxed ( t , ` ls / a
2022-04-14 06:48:52 +02:00
ls / bar
ls / foo
echo foo
echo bar
hishtory disable
echo thisisnotrecorded
2022-04-15 05:18:49 +02:00
sleep 0.5
2022-04-14 06:48:52 +02:00
hishtory enable
echo thisisrecorded ` )
2022-04-08 08:25:13 +02:00
if err != nil {
t . Fatal ( err )
}
2022-01-09 23:34:59 +01:00
if out != "foo\nbar\nthisisnotrecorded\nthisisrecorded\n" {
t . Fatalf ( "unexpected output from running commands: %#v" , out )
}
// Test querying for all commands
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-01-10 00:48:20 +01:00
expected := [ ] string { "echo thisisrecorded" , "hishtory enable" , "echo bar" , "echo foo" , "ls /foo" , "ls /bar" , "ls /a" }
for _ , item := range expected {
if ! strings . Contains ( out , item ) {
t . Fatalf ( "output is missing expected item %#v: %#v" , item , out )
}
}
2022-01-11 03:18:09 +01:00
// match, err = regexp.MatchString(`.*~/.*\s+[a-zA-Z]{3} \d+ 2022 \d\d:\d\d:\d\d PST\s+\d{1,2}ms\s+0\s+echo thisisrecorded.*`, out)
// shared.Check(t, err)
// if !match {
// t.Fatalf("output is missing the row for `echo thisisrecorded`: %v", out)
// }
2022-01-10 00:48:20 +01:00
// Test querying for a specific command
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "foo" )
2022-01-10 00:48:20 +01:00
expected = [ ] string { "echo foo" , "ls /foo" }
unexpected := [ ] string { "echo thisisrecorded" , "hishtory enable" , "echo bar" , "ls /bar" , "ls /a" }
2022-01-09 23:34:59 +01:00
for _ , item := range expected {
if ! strings . Contains ( out , item ) {
t . Fatalf ( "output is missing expected item %#v: %#v" , item , out )
}
2022-04-06 08:31:24 +02:00
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
2022-01-09 20:00:53 +01:00
}
2022-01-10 00:48:20 +01:00
for _ , item := range unexpected {
if strings . Contains ( out , item ) {
t . Fatalf ( "output is containing unexpected item %#v: %#v" , item , out )
}
}
2022-04-06 08:31:24 +02:00
2022-04-16 03:24:58 +02:00
// Add a complex command
2022-04-16 07:54:38 +02:00
complexCommand := "echo hello | grep complex | sed s/h/i/g; echo baz && echo \"fo 'o\""
2022-04-18 04:54:17 +02:00
_ , _ = tester . RunInteractiveShellRelaxed ( t , complexCommand )
2022-04-16 03:24:58 +02:00
// Query for it
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "complex" )
2022-04-16 03:24:58 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
if ! strings . Contains ( out , complexCommand ) {
t . Fatalf ( "hishtory query doesn't contain the expected complex command, out=%#v" , out )
}
2022-04-06 08:31:24 +02:00
return userSecret
2022-01-09 20:00:53 +01:00
}
2022-04-08 07:53:39 +02:00
2022-04-18 04:54:17 +02:00
func testAdvancedQuery ( t * testing . T , tester shellTester ) {
2022-04-08 07:53:39 +02:00
// Set up
defer shared . BackupAndRestore ( t ) ( )
// Install hishtory
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-08 07:53:39 +02:00
// Run some commands we can query for
2022-04-18 08:31:08 +02:00
_ , err := tester . RunInteractiveShellRelaxed ( t , ` echo nevershouldappear
2022-04-15 05:18:49 +02:00
notacommand
cd / tmp /
echo querybydir
hishtory disable ` )
2022-04-12 08:48:51 +02:00
if err != nil {
t . Fatal ( err )
}
2022-04-08 07:53:39 +02:00
2022-04-13 06:24:25 +02:00
// A super basic query just to ensure the basics are working
2022-04-18 04:54:17 +02:00
out := hishtoryQuery ( t , tester , ` echo ` )
2022-04-13 06:24:25 +02:00
if ! strings . Contains ( out , "echo querybydir" ) {
2022-04-15 05:18:49 +02:00
t . Fatalf ( "hishtory query doesn't contain result matching echo querybydir, out=%#v" , out )
2022-04-13 06:24:25 +02:00
}
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 07:53:39 +02:00
// Query based on cwd
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` cwd:/tmp ` )
2022-04-08 07:53:39 +02:00
if ! strings . Contains ( out , "echo querybydir" ) {
t . Fatalf ( "hishtory query doesn't contain result matching cwd:/tmp, out=%#v" , out )
}
if strings . Contains ( out , "nevershouldappear" ) {
t . Fatalf ( "hishtory query contains unexpected entry, out=%#v" , out )
}
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-09 03:23:17 +02:00
// Query based on cwd without the slash
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` cwd:tmp ` )
2022-04-09 03:23:17 +02:00
if ! strings . Contains ( out , "echo querybydir" ) {
t . Fatalf ( "hishtory query doesn't contain result matching cwd:tmp, out=%#v" , out )
}
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 07:53:39 +02:00
// Query based on cwd and another term
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` cwd:/tmp querybydir ` )
2022-04-08 07:53:39 +02:00
if ! strings . Contains ( out , "echo querybydir" ) {
t . Fatalf ( "hishtory query doesn't contain result matching cwd:/tmp, out=%#v" , out )
}
if strings . Contains ( out , "nevershouldappear" ) {
t . Fatalf ( "hishtory query contains unexpected entry, out=%#v" , out )
}
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 08:25:13 +02:00
// Query based on exit_code
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` exit_code:127 ` )
2022-04-08 08:25:13 +02:00
if ! strings . Contains ( out , "notacommand" ) {
t . Fatalf ( "hishtory query doesn't contain expected result, out=%#v" , out )
}
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 08:30:31 +02:00
// Query based on exit_code and something else that matches nothing
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` exit_code:127 foo ` )
2022-04-08 08:30:31 +02:00
if strings . Count ( out , "\n" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-09 03:23:17 +02:00
// Query based on before: and cwd:
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` before:2125-07-02 cwd:/tmp ` )
2022-04-09 03:23:17 +02:00
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` before:2125-07-02 cwd:tmp ` )
2022-04-09 03:23:17 +02:00
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` before:2125-07-02 cwd:mp ` )
2022-04-09 03:23:17 +02:00
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
// Query based on after: and cwd:
2022-04-19 08:07:39 +02:00
out = hishtoryQuery ( t , tester , ` after:1980-07-02 cwd:/tmp ` )
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
2022-04-09 03:23:17 +02:00
}
// Query based on after: that returns no results
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` after:2120-07-02 cwd:/tmp ` )
2022-04-09 03:23:17 +02:00
if strings . Count ( out , "\n" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-11 02:38:20 +02:00
// Manually submit an entry with a different hostname and username so we can test those atoms
entry := data . MakeFakeHistoryEntry ( "cmd_with_diff_hostname_and_username" )
entry . LocalUsername = "otheruser"
entry . Hostname = "otherhostname"
manuallySubmitHistoryEntry ( t , userSecret , entry )
// Query based on the username that exists
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` user:otheruser ` )
2022-04-11 02:38:20 +02:00
if ! strings . Contains ( out , "cmd_with_diff_hostname_and_username" ) {
t . Fatalf ( "hishtory query doesn't contain expected result, out=%#v" , out )
}
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
// Query based on the username that doesn't exist
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` user:noexist ` )
2022-04-11 02:38:20 +02:00
if strings . Count ( out , "\n" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
// Query based on the hostname
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` hostname:otherhostname ` )
2022-04-11 02:38:20 +02:00
if ! strings . Contains ( out , "cmd_with_diff_hostname_and_username" ) {
t . Fatalf ( "hishtory query doesn't contain expected result, out=%#v" , out )
}
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-12 07:36:52 +02:00
// Test filtering out a search item
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-04-12 07:36:52 +02:00
if ! strings . Contains ( out , "cmd_with_diff_hostname_and_username" ) {
t . Fatalf ( "hishtory query doesn't contain expected result, out=%#v" , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -cmd_with_diff_hostname_and_username ` )
2022-04-12 07:36:52 +02:00
if strings . Contains ( out , "cmd_with_diff_hostname_and_username" ) {
t . Fatalf ( "hishtory query contains unexpected result, out=%#v" , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -echo ` )
2022-04-12 07:36:52 +02:00
if strings . Contains ( out , "echo" ) {
t . Fatalf ( "hishtory query contains unexpected result, out=%#v" , out )
}
2022-04-18 08:31:08 +02:00
if strings . Count ( out , "\n" ) != 4 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
2022-04-12 07:36:52 +02:00
}
// Test filtering out with an atom
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -hostname:otherhostname ` )
2022-04-12 07:36:52 +02:00
if strings . Contains ( out , "cmd_with_diff_hostname_and_username" ) {
t . Fatalf ( "hishtory query contains unexpected result, out=%#v" , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -user:otheruser ` )
2022-04-12 07:36:52 +02:00
if strings . Contains ( out , "cmd_with_diff_hostname_and_username" ) {
t . Fatalf ( "hishtory query contains unexpected result, out=%#v" , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -exit_code:0 ` )
2022-04-12 07:36:52 +02:00
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
// Test filtering out a search item that also looks like it could be a search for a flag
entry = data . MakeFakeHistoryEntry ( "foo -echo" )
manuallySubmitHistoryEntry ( t , userSecret , entry )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` -echo -install ` )
2022-04-12 07:36:52 +02:00
if strings . Contains ( out , "echo" ) {
t . Fatalf ( "hishtory query contains unexpected result, out=%#v" , out )
}
2022-04-18 08:31:08 +02:00
if strings . Count ( out , "\n" ) != 4 {
2022-04-12 07:36:52 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
2022-04-08 07:53:39 +02:00
}
2022-04-09 08:47:13 +02:00
2022-04-18 04:54:17 +02:00
func testUpdate ( t * testing . T , tester shellTester ) {
2022-04-28 20:26:55 +02:00
if ! shared . IsOnline ( ) {
t . Skip ( "skipping because we're currently offline" )
}
2022-04-21 05:56:01 +02:00
if runtime . GOOS == "linux" && runtime . GOARCH == "arm64" {
t . Skip ( "skipping on linux/arm64 which is unsupported" )
}
2022-04-09 21:50:01 +02:00
// Set up
defer shared . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-09 21:50:01 +02:00
2022-04-17 08:00:04 +02:00
// Record a command before the update
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , "echo hello" )
2022-04-17 08:00:04 +02:00
2022-04-09 21:50:01 +02:00
// Check the status command
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-04-09 23:37:21 +02:00
if out != fmt . Sprintf ( "Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
2022-04-09 21:50:01 +02:00
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
// Update
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory update ` )
2022-04-17 08:00:04 +02:00
isExpected , err := regexp . MatchString ( ` Verified against tlog entry \d+\nSuccessfully updated hishtory from v0[.]Unknown to v0.\d+\n ` , out )
if err != nil {
t . Fatalf ( "regex failure: %v" , err )
}
if ! isExpected {
t . Fatalf ( "hishtory update returned unexpected out=%#v" , out )
}
// Update again and assert that it skipped the update
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory update ` )
2022-04-17 08:00:04 +02:00
if strings . Count ( out , "\n" ) != 1 || ! strings . Contains ( out , "is already installed" ) {
t . Fatalf ( "repeated hishtory update didn't skip the update, out=%#v" , out )
}
2022-04-09 21:50:01 +02:00
// Then check the status command again to confirm the update worked
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-04-09 23:48:17 +02:00
if ! strings . Contains ( out , fmt . Sprintf ( "\nEnabled: true\nSecret Key: %s\nCommit Hash: " , userSecret ) ) {
2022-04-09 21:50:01 +02:00
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
if strings . Contains ( out , "\nCommit Hash: Unknown\n" ) {
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
2022-04-17 08:00:04 +02:00
// Check that the history was preserved after the update
2022-04-19 08:07:39 +02:00
out = tester . RunInteractiveShell ( t , "hishtory export | grep -v pipefail | grep -v '/tmp/client install'" )
expectedOutput := "echo hello\nhishtory status\nhishtory update\nhishtory update\nhishtory status\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
2022-04-17 08:00:04 +02:00
}
2022-04-09 21:50:01 +02:00
}
2022-04-18 04:54:17 +02:00
func testRepeatedCommandThenQuery ( t * testing . T , tester shellTester ) {
2022-04-15 05:18:49 +02:00
// Set up
defer shared . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-15 05:18:49 +02:00
// Check the status command
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-04-15 05:18:49 +02:00
if out != fmt . Sprintf ( "Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
// Run a command many times
for i := 0 ; i < 25 ; i ++ {
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , fmt . Sprintf ( "echo mycommand-%d" , i ) )
2022-04-15 05:18:49 +02:00
}
// Check that it shows up correctly
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , ` mycommand ` )
2022-04-15 05:18:49 +02:00
if strings . Count ( out , "\n" ) != 26 {
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v" , strings . Count ( out , "\n" ) , out )
}
if strings . Count ( out , "echo mycommand" ) != 25 {
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
2022-04-18 04:54:17 +02:00
// Run a few more commands
tester . RunInteractiveShell ( t , ` echo mycommand - 30
2022-04-15 05:18:49 +02:00
echo mycommand - 31
echo mycommand - 3 ` )
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , "hishtory export | grep -v pipefail | grep -v '/tmp/client install'" )
expectedOutput := "hishtory status\necho mycommand-0\necho mycommand-1\necho mycommand-2\necho mycommand-3\necho mycommand-4\necho mycommand-5\necho mycommand-6\necho mycommand-7\necho mycommand-8\necho mycommand-9\necho mycommand-10\necho mycommand-11\necho mycommand-12\necho mycommand-13\necho mycommand-14\necho mycommand-15\necho mycommand-16\necho mycommand-17\necho mycommand-18\necho mycommand-19\necho mycommand-20\necho mycommand-21\necho mycommand-22\necho mycommand-23\necho mycommand-24\nhishtory query mycommand\necho mycommand-30\necho mycommand-31\necho mycommand-3\n"
2022-04-15 05:18:49 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
}
2022-04-18 04:54:17 +02:00
func testRepeatedCommandAndQuery ( t * testing . T , tester shellTester ) {
2022-04-15 05:18:49 +02:00
// Set up
defer shared . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-15 05:18:49 +02:00
// Check the status command
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-04-15 05:18:49 +02:00
if out != fmt . Sprintf ( "Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
// Run a command many times
for i := 0 ; i < 25 ; i ++ {
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , fmt . Sprintf ( "echo mycommand-%d" , i ) )
out = hishtoryQuery ( t , tester , fmt . Sprintf ( "mycommand-%d" , i ) )
2022-04-15 05:18:49 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query #%d has the wrong number of lines=%d, out=%#v" , i , strings . Count ( out , "\n" ) , out )
}
if strings . Count ( out , "echo mycommand" ) != 1 {
t . Fatalf ( "hishtory query #%d has the wrong number of commands=%d, out=%#v" , i , strings . Count ( out , "echo mycommand" ) , out )
}
}
}
2022-04-18 04:54:17 +02:00
func testRepeatedEnableDisable ( t * testing . T , tester shellTester ) {
2022-04-15 05:18:49 +02:00
// Set up
defer shared . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
installHishtory ( t , tester , "" )
2022-04-15 05:18:49 +02:00
// Run a command many times
for i := 0 ; i < 25 ; i ++ {
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , fmt . Sprintf ( ` echo mycommand - % d
2022-04-15 05:18:49 +02:00
hishtory disable
echo shouldnotshowup
sleep 0.5
hishtory enable ` , i ) )
2022-04-18 04:54:17 +02:00
out := hishtoryQuery ( t , tester , fmt . Sprintf ( "mycommand-%d" , i ) )
2022-04-15 05:18:49 +02:00
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query #%d has the wrong number of lines=%d, out=%#v" , i , strings . Count ( out , "\n" ) , out )
}
if strings . Count ( out , "echo mycommand" ) != 1 {
t . Fatalf ( "hishtory query #%d has the wrong number of commands=%d, out=%#v" , i , strings . Count ( out , "echo mycommand" ) , out )
}
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-04-15 05:18:49 +02:00
if strings . Contains ( out , "shouldnotshowup" ) {
t . Fatalf ( "hishtory query contains a result that should not have been recorded, out=%#v" , out )
}
}
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , "hishtory export | grep -v pipefail | grep -v '/tmp/client install'" )
expectedOutput := "echo mycommand-0\nhishtory enable\nhishtory query mycommand-0\nhishtory query\necho mycommand-1\nhishtory enable\nhishtory query mycommand-1\nhishtory query\necho mycommand-2\nhishtory enable\nhishtory query mycommand-2\nhishtory query\necho mycommand-3\nhishtory enable\nhishtory query mycommand-3\nhishtory query\necho mycommand-4\nhishtory enable\nhishtory query mycommand-4\nhishtory query\necho mycommand-5\nhishtory enable\nhishtory query mycommand-5\nhishtory query\necho mycommand-6\nhishtory enable\nhishtory query mycommand-6\nhishtory query\necho mycommand-7\nhishtory enable\nhishtory query mycommand-7\nhishtory query\necho mycommand-8\nhishtory enable\nhishtory query mycommand-8\nhishtory query\necho mycommand-9\nhishtory enable\nhishtory query mycommand-9\nhishtory query\necho mycommand-10\nhishtory enable\nhishtory query mycommand-10\nhishtory query\necho mycommand-11\nhishtory enable\nhishtory query mycommand-11\nhishtory query\necho mycommand-12\nhishtory enable\nhishtory query mycommand-12\nhishtory query\necho mycommand-13\nhishtory enable\nhishtory query mycommand-13\nhishtory query\necho mycommand-14\nhishtory enable\nhishtory query mycommand-14\nhishtory query\necho mycommand-15\nhishtory enable\nhishtory query mycommand-15\nhishtory query\necho mycommand-16\nhishtory enable\nhishtory query mycommand-16\nhishtory query\necho mycommand-17\nhishtory enable\nhishtory query mycommand-17\nhishtory query\necho mycommand-18\nhishtory enable\nhishtory query mycommand-18\nhishtory query\necho mycommand-19\nhishtory enable\nhishtory query mycommand-19\nhishtory query\necho mycommand-20\nhishtory enable\nhishtory query mycommand-20\nhishtory query\necho mycommand-21\nhishtory enable\nhishtory query mycommand-21\nhishtory query\necho mycommand-22\nhishtory enable\nhishtory query mycommand-22\nhishtory query\necho mycommand-23\nhishtory enable\nhishtory query mycommand-23\nhishtory query\necho mycommand-24\nhishtory enable\nhishtory query mycommand-24\nhishtory query\n"
2022-04-16 09:44:47 +02:00
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
2022-04-15 05:18:49 +02:00
}
}
2022-04-18 04:54:17 +02:00
func testExcludeHiddenCommand ( t * testing . T , tester shellTester ) {
2022-04-15 09:04:49 +02:00
// Set up
defer shared . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
installHishtory ( t , tester , "" )
2022-04-15 09:04:49 +02:00
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , ` echo hello1
2022-04-15 09:04:49 +02:00
echo hidden
echo hello2
echo hidden ` )
2022-04-18 04:54:17 +02:00
tester . RunInteractiveShell ( t , " echo hidden" )
2022-04-18 06:04:44 +02:00
out := hishtoryQuery ( t , tester , "-pipefail" )
if strings . Count ( out , "\n" ) != 3 {
2022-04-19 06:28:41 +02:00
t . Fatalf ( "hishtory query has the wrong number of lines=%d, out=%#v, bash hishtory file=%#v" , strings . Count ( out , "\n" ) , out , tester . RunInteractiveShell ( t , "cat ~/.bash_history" ) )
2022-04-15 09:04:49 +02:00
}
if strings . Count ( out , "echo hello" ) != 2 {
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
if strings . Count ( out , "echo hello1" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
if strings . Count ( out , "echo hello2" ) != 1 {
t . Fatalf ( "hishtory query has the wrong number of commands=%d, out=%#v" , strings . Count ( out , "echo mycommand" ) , out )
}
if strings . Contains ( out , "hidden" ) {
t . Fatalf ( "hishtory query contains a result that should not have been recorded, out=%#v" , out )
}
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , "hishtory export | grep -v pipefail | grep -v '/tmp/client install'" )
2022-04-18 06:04:44 +02:00
expectedOutput := "echo hello1\necho hello2\n"
2022-04-15 09:04:49 +02:00
if out != expectedOutput {
t . Fatalf ( "hishtory export has unexpected output=%#v" , out )
}
}
2022-04-17 20:24:55 +02:00
func getPidofCommand ( ) string {
if runtime . GOOS == "darwin" {
// MacOS doesn't have pidof by default
return "pgrep"
}
return "pidof"
}
2022-04-15 05:18:49 +02:00
func waitForBackgroundSavesToComplete ( t * testing . T ) {
for i := 0 ; i < 20 ; i ++ {
2022-04-17 20:24:55 +02:00
cmd := exec . Command ( getPidofCommand ( ) , "hishtory" )
2022-04-15 05:18:49 +02:00
var stdout bytes . Buffer
var stderr bytes . Buffer
cmd . Stdout = & stdout
cmd . Stderr = & stderr
err := cmd . Run ( )
if err != nil && err . Error ( ) != "exit status 1" {
t . Fatalf ( "failed to check if hishtory was running: %v, stdout=%#v, stderr=%#v" , err , stdout . String ( ) , stderr . String ( ) )
}
if ! strings . Contains ( stdout . String ( ) , "\n" ) {
2022-04-21 05:56:01 +02:00
// pidof had no output, so hishtory isn't running and we're done waiting
time . Sleep ( 1000 * time . Millisecond )
2022-04-15 05:18:49 +02:00
return
}
time . Sleep ( 50 * time . Millisecond )
}
t . Fatalf ( "failed to wait until hishtory wasn't running" )
}
2022-04-18 04:54:17 +02:00
func hishtoryQuery ( t * testing . T , tester shellTester , query string ) string {
return tester . RunInteractiveShell ( t , "hishtory query " + query )
2022-04-15 05:18:49 +02:00
}
2022-04-11 02:38:20 +02:00
// TODO: Maybe a dedicated unit test for retrieveAdditionalEntriesFromRemote
2022-04-09 08:47:13 +02:00
2022-04-11 02:38:20 +02:00
func manuallySubmitHistoryEntry ( t * testing . T , userSecret string , entry data . HistoryEntry ) {
encEntry , err := data . EncryptHistoryEntry ( userSecret , entry )
2022-04-09 08:47:13 +02:00
shared . Check ( t , err )
2022-04-11 02:38:20 +02:00
jsonValue , err := json . Marshal ( [ ] shared . EncHistoryEntry { encEntry } )
2022-04-09 08:47:13 +02:00
shared . Check ( t , err )
2022-04-28 20:26:55 +02:00
resp , err := http . Post ( "http://localhost:8080/api/v1/submit" , "application/json" , bytes . NewBuffer ( jsonValue ) )
2022-04-09 08:47:13 +02:00
shared . Check ( t , err )
2022-04-11 02:38:20 +02:00
if resp . StatusCode != 200 {
t . Fatalf ( "failed to submit result to backend, status_code=%d" , resp . StatusCode )
2022-04-09 08:47:13 +02:00
}
}
2022-04-15 05:18:49 +02:00
2022-04-19 07:07:10 +02:00
func testTimestampsAreReasonablyCorrect ( t * testing . T , tester shellTester ) {
// Setup
defer shared . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
// Record a command
out := tester . RunInteractiveShell ( t , "echo hello" )
if out != "hello\n" {
t . Fatalf ( "running echo hello had unexpected out=%#v" , out )
}
// Query for it and check that the timestamp that gets recorded looks reasonable
out = hishtoryQuery ( t , tester , "echo hello" )
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
expectedDate := time . Now ( ) . Format ( "Jan 2 2006" )
if ! strings . Contains ( out , expectedDate ) {
t . Fatalf ( "hishtory query has an incorrect date: out=%#v" , out )
}
}
2022-04-19 07:45:07 +02:00
func testTableDisplayCwd ( t * testing . T , tester shellTester ) {
// Setup
defer shared . BackupAndRestore ( t ) ( )
installHishtory ( t , tester , "" )
// Record a command
out := tester . RunInteractiveShell ( t , ` cd ~ / . hishtory /
echo hello
cd / tmp /
echo other ` )
if out != "hello\nother\n" {
t . Fatalf ( "running echo hello had unexpected out=%#v" , out )
}
// Query for it and check that the directory gets recorded correctly
out = hishtoryQuery ( t , tester , "echo hello" )
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
2022-04-20 04:21:39 +02:00
if ! strings . Contains ( out , "~/.hishtory" ) {
2022-04-19 07:45:07 +02:00
t . Fatalf ( "hishtory query has an incorrect CWD: out=%#v" , out )
}
out = hishtoryQuery ( t , tester , "echo other" )
if strings . Count ( out , "\n" ) != 2 {
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
2022-04-20 04:21:39 +02:00
if ! strings . Contains ( out , "/tmp" ) {
2022-04-19 07:45:07 +02:00
t . Fatalf ( "hishtory query has an incorrect CWD: out=%#v" , out )
}
}
2022-04-18 04:54:17 +02:00
func testHishtoryBackgroundSaving ( t * testing . T , tester shellTester ) {
2022-04-28 20:26:55 +02:00
if runtime . GOOS == "darwin" && runtime . GOARCH == "arm64" {
t . Skip ( "skip testing background saving since it is too flakey on M1" )
}
2022-04-15 05:18:49 +02:00
// Setup
defer shared . BackupAndRestore ( t ) ( )
// Test install with an unset HISHTORY_TEST var so that we save in the background (this is likely to be flakey!)
2022-04-18 04:54:17 +02:00
out := tester . RunInteractiveShell ( t , ` unset HISHTORY_TEST
2022-04-15 05:18:49 +02:00
go build - o / tmp / client
/ tmp / client install ` )
r := regexp . MustCompile ( ` Setting secret hishtory key to (.*) ` )
matches := r . FindStringSubmatch ( out )
if len ( matches ) != 2 {
t . Fatalf ( "Failed to extract userSecret from output: matches=%#v" , matches )
}
userSecret := matches [ 1 ]
// Assert that config.sh isn't the dev version
2022-04-16 09:56:08 +02:00
homedir , err := os . UserHomeDir ( )
if err != nil {
t . Fatalf ( "failed to get homedir: %v" , err )
}
dat , err := os . ReadFile ( path . Join ( homedir , shared . HISHTORY_PATH , "config.sh" ) )
if err != nil {
t . Fatalf ( "failed to read config.sh: %v" , err )
}
if strings . Contains ( string ( dat ) , "except it doesn't run the save process in the background" ) {
t . Fatalf ( "config.sh is the testing version when it shouldn't be, config.sh=%#v" , dat )
}
2022-04-15 05:18:49 +02:00
// Test the status subcommand
2022-04-18 04:54:17 +02:00
out = tester . RunInteractiveShell ( t , ` hishtory status ` )
2022-04-15 05:18:49 +02:00
if out != fmt . Sprintf ( "Hishtory: v0.Unknown\nEnabled: true\nSecret Key: %s\nCommit Hash: Unknown\n" , userSecret ) {
t . Fatalf ( "status command has unexpected output: %#v" , out )
}
// Test recording commands
2022-04-20 06:05:54 +02:00
out , err = tester . RunInteractiveShellRelaxed ( t , ` ls / a
2022-04-15 05:18:49 +02:00
echo foo ` )
if err != nil {
t . Fatal ( err )
}
if out != "foo\n" {
t . Fatalf ( "unexpected output from running commands: %#v" , out )
}
// Test querying for all commands
2022-04-15 20:20:23 +02:00
waitForBackgroundSavesToComplete ( t )
2022-04-22 07:25:24 +02:00
time . Sleep ( time . Second )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "" )
2022-04-15 05:18:49 +02:00
expected := [ ] string { "echo foo" , "ls /a" }
for _ , item := range expected {
if ! strings . Contains ( out , item ) {
t . Fatalf ( "output is missing expected item %#v: %#v" , item , out )
}
}
// Test querying for a specific command
2022-04-15 20:20:23 +02:00
waitForBackgroundSavesToComplete ( t )
2022-04-18 04:54:17 +02:00
out = hishtoryQuery ( t , tester , "foo" )
2022-04-15 05:18:49 +02:00
if ! strings . Contains ( out , "echo foo" ) {
t . Fatalf ( "output doesn't contain the expected item, out=%#v" , out )
}
if strings . Contains ( out , "ls /a" ) {
t . Fatalf ( "output contains unexpected item, out=%#v" , out )
}
}
2022-04-16 10:09:25 +02:00
2022-04-18 04:54:17 +02:00
func testDisplayTable ( t * testing . T , tester shellTester ) {
2022-04-16 10:09:25 +02:00
// Setup
defer shared . BackupAndRestore ( t ) ( )
2022-04-18 04:54:17 +02:00
userSecret := installHishtory ( t , tester , "" )
2022-04-16 10:09:25 +02:00
// Submit two fake entries
2022-04-16 19:46:57 +02:00
tmz , err := time . LoadLocation ( "America/Los_Angeles" )
if err != nil {
t . Fatalf ( "failed to load timezone: %v" , err )
}
2022-04-16 10:09:25 +02:00
entry1 := data . MakeFakeHistoryEntry ( "table_cmd1" )
2022-04-16 19:46:57 +02:00
entry1 . StartTime = time . Unix ( 1650096186 , 0 ) . In ( tmz )
entry1 . EndTime = time . Unix ( 1650096190 , 0 ) . In ( tmz )
2022-04-16 10:09:25 +02:00
manuallySubmitHistoryEntry ( t , userSecret , entry1 )
entry2 := data . MakeFakeHistoryEntry ( "table_cmd2" )
2022-04-16 19:46:57 +02:00
entry2 . StartTime = time . Unix ( 1650096196 , 0 ) . In ( tmz )
entry2 . EndTime = time . Unix ( 1650096220 , 0 ) . In ( tmz )
2022-04-16 10:09:25 +02:00
entry2 . CurrentWorkingDirectory = "~/foo/"
entry2 . ExitCode = 3
manuallySubmitHistoryEntry ( t , userSecret , entry2 )
// Query and check the table
2022-04-18 04:54:17 +02:00
out := hishtoryQuery ( t , tester , "table" )
2022-04-21 07:13:10 +02:00
expectedOutput1 := "Hostname CWD Timestamp Runtime Exit Code Command \nlocalhost ~/foo/ Apr 16 2022 01:03:16 -0700 24s 3 table_cmd2 \nlocalhost /tmp/ Apr 16 2022 01:03:06 -0700 4s 2 table_cmd1 \n"
expectedOutput2 := "Hostname CWD Timestamp Runtime Exit Code Command \nlocalhost ~/foo/ Apr 16 2022 01:03:16 PDT 24s 3 table_cmd2 \nlocalhost /tmp/ Apr 16 2022 01:03:06 PDT 4s 2 table_cmd1 \n"
if out != expectedOutput1 && out != expectedOutput2 {
t . Fatalf ( "hishtory query table test mismatch out=%#v" , out )
2022-04-16 10:09:25 +02:00
}
}
2022-05-02 04:37:26 +02:00
func testRequestAndReceiveDbDump ( t * testing . T , tester shellTester ) {
// Set up
defer shared . BackupAndRestore ( t ) ( )
secretKey := installHishtory ( t , tester , "" )
2022-05-23 04:45:46 +02:00
// Confirm there are no pending dump requests
resp , err := lib . ApiGet ( "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) )
if err != nil {
t . Fatalf ( "failed to get pending dump requests: %v" , err )
}
if string ( resp ) != "[]" {
t . Fatalf ( "There are pending dump requests! user_id=%#v, resp=%#v" , data . UserId ( secretKey ) , string ( resp ) )
}
2022-05-02 04:37:26 +02:00
// Record two commands and then query for them
out := tester . RunInteractiveShell ( t , ` echo hello
2022-05-23 04:45:46 +02:00
echo other ` )
if out != "hello\nother\n" {
t . Fatalf ( "running echo had unexpected out=%#v" , out )
}
2022-05-02 04:37:26 +02:00
// Query for it and check that the directory gets recorded correctly
out = hishtoryQuery ( t , tester , "echo" )
if strings . Count ( out , "\n" ) != 3 {
t . Fatalf ( "hishtory query has unexpected number of lines: out=%#v" , out )
}
if ! strings . Contains ( out , "echo hello" ) {
t . Fatalf ( "hishtory query doesn't contain expected command, out=%#v" , out )
}
if ! strings . Contains ( out , "echo other" ) {
t . Fatalf ( "hishtory query doesn't contain expected command, out=%#v" , out )
}
// Back up this copy
restoreFirstInstallation := shared . BackupAndRestoreWithId ( t , "-install1" )
// Wipe the DB to simulate entries getting deleted because they've already been read and expired
2022-05-23 04:45:46 +02:00
_ , err = lib . ApiGet ( "/api/v1/wipe-db" )
2022-05-02 04:37:26 +02:00
if err != nil {
t . Fatalf ( "failed to wipe the DB: %v" , err )
}
// Install a new one (with the same secret key but a diff device id)
installHishtory ( t , tester , secretKey )
2022-05-23 04:45:46 +02:00
// Confirm there are pending dump requests
resp , err = lib . ApiGet ( "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) )
if err != nil {
t . Fatalf ( "failed to get pending dump requests: %v" , err )
}
if string ( resp ) == "[]" {
t . Fatalf ( "There are no pending dump requests! user_id=%#v, resp=%#v" , data . UserId ( secretKey ) , string ( resp ) )
}
2022-05-02 04:37:26 +02:00
// Check that the new one doesn't have the commands yet
out = hishtoryQuery ( t , tester , "echo" )
if strings . Count ( out , "\n" ) != 1 {
t . Fatalf ( "hishtory query has unexpected number of lines, should contain no entries: out=%#v" , out )
}
if strings . Contains ( out , "echo hello" ) || strings . Contains ( "echo other" , out ) {
t . Fatalf ( "hishtory query contains unexpected command, out=%#v" , out )
}
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
if out != "hishtory query echo\n" {
t . Fatalf ( "hishtory export has unexpected out=%#v" , out )
}
// Restore the first copy
restoreSecondInstallation := shared . BackupAndRestoreWithId ( t , "-install2" )
restoreFirstInstallation ( )
// Confirm it still has the correct entries via hishtory export (and this runs a command to trigger it to dump the DB)
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-05-23 04:45:46 +02:00
expectedOutput := "echo hello\necho other\nhishtory query echo\nhishtory query echo\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
}
// Confirm there are no pending dump requests
resp , err = lib . ApiGet ( "/api/v1/get-dump-requests?user_id=" + data . UserId ( secretKey ) )
if err != nil {
t . Fatalf ( "failed to get pending dump requests: %v" , err )
}
if string ( resp ) != "[]" {
t . Fatalf ( "There are pending dump requests! user_id=%#v, resp=%#v" , data . UserId ( secretKey ) , string ( resp ) )
2022-05-02 04:37:26 +02:00
}
// Restore the second copy and confirm it has the commands
restoreSecondInstallation ( )
out = hishtoryQuery ( t , tester , "ech" )
if strings . Count ( out , "\n" ) != 5 {
t . Fatalf ( "hishtory query has unexpected number of lines=%d: out=%#v" , strings . Count ( out , "\n" ) , out )
}
expected := [ ] string { "echo hello" , "echo other" }
for _ , item := range expected {
if ! strings . Contains ( out , item ) {
t . Fatalf ( "output is missing expected item %#v: %#v" , item , out )
}
if strings . Count ( out , item ) != 1 {
t . Fatalf ( "output has %#v in it multiple times! out=%#v" , item , out )
}
}
// And check hishtory export too for good measure
out = tester . RunInteractiveShell ( t , ` hishtory export | grep -v pipefail ` )
2022-05-23 04:45:46 +02:00
expectedOutput = "echo hello\necho other\nhishtory query echo\nhishtory query echo\nhishtory query ech\n"
if diff := cmp . Diff ( expectedOutput , out ) ; diff != "" {
t . Fatalf ( "hishtory export mismatch (-expected +got):\n%s\nout=%#v" , diff , out )
2022-05-02 04:37:26 +02:00
}
}
// TODO: write a test that runs hishtroy export | grep -v pipefail and then see if that shows up in query/export, I think there is weird behavior here