91
integrationTest/NOTES.txt
Normal file
91
integrationTest/NOTES.txt
Normal file
@@ -0,0 +1,91 @@
|
||||
|
||||
This should accept VCS-type and --crypto flags.
|
||||
Then a shell script should run various combinations of VCS and crypters.
|
||||
|
||||
# Startup
|
||||
* Create a repo (git, none)
|
||||
|
||||
# Test basic operations:
|
||||
* As Alice:
|
||||
* initialize blackbox, add her keys to it, see that the usual files
|
||||
exist. See her name in bb-admins.txt
|
||||
* encrypt a file, see that the plaintext is deleted, see the file in bb-files.txt
|
||||
* decrypt the file, see the original plaintext is recovered.
|
||||
* Encrypt a file --noshred.
|
||||
* Decrypt the file, it should fail as the plaintext exists.
|
||||
* Remove the plaintext.
|
||||
* Decrypt the file, it should fail as the plaintext exists.
|
||||
|
||||
# Test hand-off from Alice to Bob.
|
||||
* As Bob
|
||||
* add himself to the admins.
|
||||
* As Alice
|
||||
* Update-all-files
|
||||
* Create a new file. Encrypt it.
|
||||
* As Bob
|
||||
* Decrypt both files
|
||||
* Verify contents of the new file, and the file from previous.
|
||||
* Create a new file. Encrypt it.
|
||||
* As Alice:
|
||||
* Decrypt all files.
|
||||
* Verify contents of the 3 plaintext files.
|
||||
|
||||
# Test a git-less directory
|
||||
* Copy the old repo somewhere. Remove the .git directory.
|
||||
* As Alice:
|
||||
* Decrypt all
|
||||
* Verify plaintext contents
|
||||
|
||||
# Test post-deploy with/without GID
|
||||
* Back at the original repo:
|
||||
* Shred all
|
||||
* Run post-deploy. Verify.
|
||||
* Shred all
|
||||
* Run post-deploy with a custom GID. Verify.
|
||||
|
||||
# Test removing an admin
|
||||
* As Bob:
|
||||
* removes Alice. (Verify)
|
||||
* Re-encrypt
|
||||
* Decrypt all & verify.
|
||||
* As alice
|
||||
* Decrypting should fail.
|
||||
|
||||
# Test funny names and paths
|
||||
* my/path/to/relsecrets.txt
|
||||
* cwd=other/place ../../my/path/to/relsecrets.txt
|
||||
* !important!.txt
|
||||
* #andpounds.txt
|
||||
* stars*bars?.txt
|
||||
* space space.txt
|
||||
* Do add/encrypt/decrypt
|
||||
* Do blackbox_update_all_files
|
||||
* Do remove them all
|
||||
|
||||
# When people start asking for commands to work with relative paths
|
||||
# Test from outside the repo
|
||||
* mkdir ../other/place
|
||||
* cd ../other/place
|
||||
* decrypt ../../secret1.txt
|
||||
* encrypt ../../secret1.txt
|
||||
|
||||
# Test specific commands:
|
||||
# blackbox admins list
|
||||
# blackbox file list
|
||||
# blackbox status --name-only (create 1 of each "type")
|
||||
# blackbox status --type=FOO
|
||||
|
||||
# These should all fail:
|
||||
# blackbox file list --all
|
||||
# blackbox file list blah
|
||||
# blackbox shred list --all
|
||||
# blackbox shred list blah
|
||||
|
||||
|
||||
|
||||
rm -rf /tmp/bbhome-* && BLACKBOX_DEBUG=true go test -verbose -long -nocleanup
|
||||
rm -rf /tmp/bbhome-* && go test -long -nocleanup
|
||||
|
||||
( gbb && cd cmd/blackbox && go install ) && blackbox
|
||||
|
||||
cd /tmp && rm -rf /tmp/bbhome-* ; mkdir /tmp/bbhome-1 ; cd /tmp/bbhome-1 && git init ; gitmeWork ; ( gbb && cd cmd/blackbox && go install ) && blackbox init yes && gitmeWork ; git commit -mm -a ; blackbox admin add tlimoncelli ; git commit -mnewadmin -a ; echo secrt > secret.txt ; blackbox file add secret.txt
|
||||
55
integrationTest/README.txt
Normal file
55
integrationTest/README.txt
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
Each test does the following:
|
||||
1. Copy the files from testdata/NNNN
|
||||
2. Run the command in test_NNNN.sh
|
||||
3.
|
||||
|
||||
|
||||
TEST ENROLLMENT:
|
||||
|
||||
PHASE 'Alice creates a repo. She creates secret.txt.'
|
||||
PHASE 'Alice wants to be part of the secret system.'
|
||||
PHASE 'She creates a GPG key...'
|
||||
PHASE 'Initializes BB...'
|
||||
PHASE 'and adds herself as an admin.'
|
||||
PHASE 'Bob arrives.'
|
||||
PHASE 'Bob creates a gpg key.'
|
||||
PHASE 'Alice does the second part to enroll bob.'
|
||||
PHASE 'She enrolls bob.'
|
||||
PHASE 'She enrolls secrets.txt.'
|
||||
PHASE 'She decrypts secrets.txt.'
|
||||
PHASE 'She edits secrets.txt.'
|
||||
PHASE 'Alice copies files to a non-repo directory. (NO REPO)'
|
||||
PHASE 'Alice shreds these non-repo files. (NO REPO)'
|
||||
PHASE 'Alice decrypts secrets.txt (NO REPO).'
|
||||
PHASE 'Alice edits secrets.txt. (NO REPO EDIT)'
|
||||
PHASE 'Alice decrypts secrets.txt (NO REPO EDIT).'
|
||||
PHASE 'appears.'
|
||||
#PHASE 'Bob makes sure he has all new keys.'
|
||||
|
||||
TEST INDIVIDUAL COMMANDS:
|
||||
|
||||
PHASE 'Bob postdeploys... default.'
|
||||
PHASE 'Bob postdeploys... with a GID.'
|
||||
PHASE 'Bob cleans up the secret.'
|
||||
PHASE 'Bob removes Alice.'
|
||||
PHASE 'Bob reencrypts files so alice can not access them.'
|
||||
PHASE 'Bob decrypts secrets.txt.'
|
||||
PHASE 'Bob edits secrets.txt.'
|
||||
PHASE 'Bob decrypts secrets.txt VERSION 3.'
|
||||
PHASE 'Bob exposes a secret in the repo.'
|
||||
PHASE 'Bob corrects it by registering it.'
|
||||
PHASE 'Bob enrolls my/path/to/relsecrets.txt.'
|
||||
PHASE 'Bob decrypts relsecrets.txt.'
|
||||
PHASE 'Bob enrolls !important!.txt'
|
||||
PHASE 'Bob enrolls #andpounds.txt'
|
||||
PHASE 'Bob enrolls stars*bars?.txt'
|
||||
PHASE 'Bob enrolls space space.txt'
|
||||
PHASE 'Bob checks out stars*bars?.txt.'
|
||||
PHASE 'Bob checks out space space.txt.'
|
||||
PHASE 'Bob shreds all exposed files.'
|
||||
PHASE 'Bob updates all files.'
|
||||
PHASE 'Bob DEregisters mistake.txt'
|
||||
PHASE 'Bob enrolls multiple files: multi1.txt and multi2.txt'
|
||||
PHASE 'Alice returns. She should be locked out'
|
||||
PHASE 'Alice tries to decrypt secret.txt. Is blocked.'
|
||||
68
integrationTest/asserts.go
Normal file
68
integrationTest/asserts.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/andreyvit/diff"
|
||||
)
|
||||
|
||||
func assertFileMissing(t *testing.T, name string) {
|
||||
t.Helper()
|
||||
_, err := os.Stat(name)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("assertFileMissing failed: %v exists", name)
|
||||
}
|
||||
t.Fatalf("assertFileMissing: %q: %v", name, err)
|
||||
}
|
||||
|
||||
func assertFileExists(t *testing.T, name string) {
|
||||
t.Helper()
|
||||
_, err := os.Stat(name)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
t.Fatalf("assertFileExists failed: %v not exist", name)
|
||||
}
|
||||
t.Fatalf("assertFileExists: file can't be accessed: %v: %v", name, err)
|
||||
}
|
||||
|
||||
func assertFileEmpty(t *testing.T, name string) {
|
||||
t.Helper()
|
||||
c, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(c) != 0 {
|
||||
t.Fatalf("got=%v want=%v: %v", len(c), 0, name)
|
||||
}
|
||||
}
|
||||
|
||||
func assertFileContents(t *testing.T, name string, contents string) {
|
||||
t.Helper()
|
||||
c, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if w, g := contents, string(c); w != g {
|
||||
t.Errorf("assertFileContents(%q) mismatch (-got +want):\n%s",
|
||||
name, diff.LineDiff(g, w))
|
||||
}
|
||||
}
|
||||
|
||||
func assertFilePerms(t *testing.T, name string, perms os.FileMode) {
|
||||
t.Helper()
|
||||
s, err := os.Stat(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.Mode() != perms {
|
||||
t.Fatalf("got=%#o want=%#o: %v", s.Mode(), perms, name)
|
||||
}
|
||||
}
|
||||
343
integrationTest/integration_test.go
Normal file
343
integrationTest/integration_test.go
Normal file
@@ -0,0 +1,343 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/StackExchange/blackbox/v2/pkg/bblog"
|
||||
_ "github.com/StackExchange/blackbox/v2/pkg/bblog"
|
||||
_ "github.com/StackExchange/blackbox/v2/pkg/vcs/_all"
|
||||
)
|
||||
|
||||
var vcsToTest = flag.String("testvcs", "GIT", "VCS to test")
|
||||
var longTests = flag.Bool("long", false, "Run long version of tests")
|
||||
|
||||
//var crypterToTest = flag.String("crypter", "GnuPG", "crypter to test")
|
||||
|
||||
func init() {
|
||||
testing.Init()
|
||||
flag.Parse()
|
||||
|
||||
op, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
originPath = op
|
||||
}
|
||||
|
||||
func compile(t *testing.T) {
|
||||
if PathToBlackBox() != "" {
|
||||
// It's been compiled already.
|
||||
return
|
||||
}
|
||||
// Make sure we have the latest binary
|
||||
fmt.Println("========== Compiling")
|
||||
cmd := exec.Command("go", "build", "-o", "../bbintegration", "../cmd/blackbox")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Fatalf("setup_compile: %v", err)
|
||||
}
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
SetPathToBlackBox(filepath.Join(cwd, "../bbintegration"))
|
||||
}
|
||||
|
||||
func setup(t *testing.T) {
|
||||
logDebug := bblog.GetDebug(*verbose)
|
||||
|
||||
logDebug.Printf("flag.testvcs is %v", *vcsToTest)
|
||||
vh := getVcs(t, *vcsToTest)
|
||||
logDebug.Printf("Using BLACKBOX_VCS=%v", vh.Name())
|
||||
os.Setenv("BLACKBOX_VCS", vh.Name())
|
||||
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
if !*longTests {
|
||||
return
|
||||
}
|
||||
compile(t)
|
||||
makeHomeDir(t, "init")
|
||||
|
||||
// Only zero or one args are permitted.
|
||||
invalidArgs(t, "init", "one", "two")
|
||||
invalidArgs(t, "init", "one", "two", "three")
|
||||
|
||||
runBB(t, "init", "yes")
|
||||
assertFileEmpty(t, ".blackbox/blackbox-admins.txt")
|
||||
assertFileEmpty(t, ".blackbox/blackbox-files.txt")
|
||||
assertFilePerms(t, ".blackbox/blackbox-admins.txt", 0o640)
|
||||
assertFilePerms(t, ".blackbox/blackbox-files.txt", 0o640)
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
if !*longTests {
|
||||
return
|
||||
}
|
||||
compile(t)
|
||||
makeHomeDir(t, "init")
|
||||
|
||||
runBB(t, "init", "yes")
|
||||
createDummyFilesAdmin(t)
|
||||
checkOutput("000-admin-list.txt", t, "admin", "list")
|
||||
checkOutput("000-file-list.txt", t, "file", "list")
|
||||
|
||||
invalidArgs(t, "file", "list", "extra")
|
||||
invalidArgs(t, "admin", "list", "extra")
|
||||
}
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
if !*longTests {
|
||||
return
|
||||
}
|
||||
compile(t)
|
||||
makeHomeDir(t, "init")
|
||||
|
||||
runBB(t, "init", "yes")
|
||||
createFilesStatus(t)
|
||||
checkOutput("000-status.txt", t, "status")
|
||||
}
|
||||
|
||||
func TestShred(t *testing.T) {
|
||||
if !*longTests {
|
||||
return
|
||||
}
|
||||
compile(t)
|
||||
makeHomeDir(t, "shred")
|
||||
runBB(t, "init", "yes")
|
||||
|
||||
makeFile(t, "shredme.txt", "File with SHREDME in it.\n")
|
||||
assertFileExists(t, "shredme.txt")
|
||||
runBB(t, "shred", "shredme.txt")
|
||||
assertFileMissing(t, "shredme.txt")
|
||||
}
|
||||
|
||||
func TestStatus_notreg(t *testing.T) {
|
||||
if !*longTests {
|
||||
return
|
||||
}
|
||||
compile(t)
|
||||
makeHomeDir(t, "init")
|
||||
|
||||
runBB(t, "init", "yes")
|
||||
createFilesStatus(t)
|
||||
checkOutput("status-noreg.txt", t, "status", "status-ENCRYPTED.txt", "blah.txt")
|
||||
}
|
||||
|
||||
// TestHard tests the functions using a fake homedir and repo.
|
||||
func TestHard(t *testing.T) {
|
||||
if !*longTests {
|
||||
return
|
||||
}
|
||||
// These are basic tests that work on a fake repo.
|
||||
// The repo has mostly real data, except any .gpg file
|
||||
// is just garbage.
|
||||
compile(t)
|
||||
setup(t)
|
||||
|
||||
for _, cx := range []struct{ subname, prefix string }{
|
||||
//{subname: ".", prefix: "."},
|
||||
{subname: "mysub", prefix: ".."},
|
||||
} {
|
||||
subname := cx.subname
|
||||
prefix := cx.prefix
|
||||
_ = prefix
|
||||
|
||||
phase("========== SUBDIR = " + subname + " ==========")
|
||||
|
||||
makeHomeDir(t, "BasicAlice")
|
||||
|
||||
plaintextFoo := "I am the foo.txt file!\n"
|
||||
plainAltered := "I am the altered file!\n"
|
||||
|
||||
runBB(t, "testing_init") // Runs "git init" or equiv
|
||||
assertFileExists(t, ".git")
|
||||
runBB(t, "init", "yes") // Creates .blackbox or equiv
|
||||
|
||||
if subname != "." {
|
||||
err := os.Mkdir(subname, 0770)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("hard-mk-home %q: %v", subname, err))
|
||||
}
|
||||
}
|
||||
olddir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Chdir(subname)
|
||||
os.Chdir(olddir)
|
||||
|
||||
phase("Alice creates a GPG key")
|
||||
gpgdir := makeAdmin(t, "alice", "Alice Example", "alice@example.com")
|
||||
become(t, "alice")
|
||||
|
||||
phase("Alice enrolls as an admin")
|
||||
//os.Chdir(subname)
|
||||
runBB(t, "admin", "add", "alice@example.com", gpgdir)
|
||||
//os.Chdir(olddir)
|
||||
|
||||
// encrypt
|
||||
phase("Alice registers foo.txt")
|
||||
makeFile(t, "foo.txt", plaintextFoo)
|
||||
//os.Chdir(subname)
|
||||
//runBB(t, "file", "add", "--shred", filepath.Join(prefix, "foo.txt"))
|
||||
runBB(t, "file", "add", "--shred", "foo.txt")
|
||||
//os.Chdir(olddir)
|
||||
// "file add" encrypts the file.
|
||||
// We shred the plaintext so that we are sure that when Decrypt runs,
|
||||
// we can verify the contents wasn't just sitting there all the time.
|
||||
assertFileMissing(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
|
||||
phase("Alice decrypts foo.txt")
|
||||
// decrypt
|
||||
//os.Chdir(subname)
|
||||
runBB(t, "decrypt", "foo.txt")
|
||||
//runBB(t, "decrypt", filepath.Join(prefix, "foo.txt"))
|
||||
//os.Chdir(olddir)
|
||||
assertFileExists(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
assertFileContents(t, "foo.txt", plaintextFoo)
|
||||
|
||||
// encrypts (without shredding)
|
||||
phase("Alice encrypts foo.txt (again)")
|
||||
runBB(t, "encrypt", "foo.txt")
|
||||
assertFileExists(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
assertFileContents(t, "foo.txt", plaintextFoo)
|
||||
|
||||
// reencrypt
|
||||
phase("Alice reencrypts")
|
||||
checkOutput("basic-status.txt", t, "status")
|
||||
runBB(t, "reencrypt", "--overwrite", "foo.txt")
|
||||
|
||||
// Test variations of cat
|
||||
|
||||
// foo.txt=plain result=plain
|
||||
phase("Alice cats plain:plain")
|
||||
makeFile(t, "foo.txt", plaintextFoo)
|
||||
assertFileExists(t, "foo.txt")
|
||||
runBB(t, "encrypt", "foo.txt")
|
||||
assertFileExists(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
checkOutput("alice-cat-plain.txt", t, "cat", "foo.txt")
|
||||
assertFileExists(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
|
||||
// foo.txt=altered result=plain
|
||||
phase("Alice cats altered:plain")
|
||||
makeFile(t, "foo.txt", plainAltered)
|
||||
assertFileExists(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
checkOutput("alice-cat-plain.txt", t, "cat", "foo.txt")
|
||||
assertFileExists(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
|
||||
// foo.txt=missing result=plain
|
||||
phase("Alice cats missing:plain")
|
||||
removeFile(t, "foo.txt")
|
||||
assertFileMissing(t, "foo.txt")
|
||||
assertFileMissing(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
checkOutput("alice-cat-plain.txt", t, "cat", "foo.txt")
|
||||
assertFileMissing(t, "foo.txt")
|
||||
assertFileExists(t, "foo.txt.gpg")
|
||||
|
||||
// Chapter 2: Bob
|
||||
// Alice adds Bob.
|
||||
// Bob encrypts a file.
|
||||
// Bob makes sure he can decrypt alice's file.
|
||||
// Bob removes Alice.
|
||||
// Alice verifies she CAN'T decrypt files.
|
||||
// Bob adds Alice back.
|
||||
// Alice verifies she CAN decrypt files.
|
||||
// Bob adds an encrypted file by mistake, "bb add" and fixes it.
|
||||
// Bob corrupts the blackbox-admins.txt file, verifies that commands fail.
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestEvilFilenames verifies commands work with "difficult" file names
|
||||
func TestEvilFilenames(t *testing.T) {
|
||||
if !*longTests {
|
||||
return
|
||||
}
|
||||
compile(t)
|
||||
setup(t)
|
||||
makeHomeDir(t, "Mallory")
|
||||
|
||||
runBB(t, "testing_init") // Runs "git init" or equiv
|
||||
assertFileExists(t, ".git")
|
||||
runBB(t, "init", "yes") // Creates .blackbox or equiv
|
||||
|
||||
phase("Malory creates a GPG key")
|
||||
gpgdir := makeAdmin(t, "mallory", "Mallory Evil", "mallory@example.com")
|
||||
become(t, "mallory")
|
||||
|
||||
phase("Mallory enrolls as an admin")
|
||||
runBB(t, "admin", "add", "mallory@example.com", gpgdir)
|
||||
|
||||
_ = os.MkdirAll("my/path/to", 0o770)
|
||||
_ = os.Mkdir("other", 0o770)
|
||||
|
||||
for i, name := range []string{
|
||||
"!important!.txt",
|
||||
"#andpounds.txt",
|
||||
"stars*bars?.txt",
|
||||
"space space.txt",
|
||||
"tab\ttab.txt",
|
||||
"ret\rret.txt",
|
||||
"smile😁eyes",
|
||||
"¡que!",
|
||||
"thé",
|
||||
"pound£",
|
||||
"*.go",
|
||||
"rm -f erase ; echo done",
|
||||
`smile☺`,
|
||||
`dub𝓦`,
|
||||
"my/path/to/relsecrets.txt",
|
||||
//"my/../my/path/../path/to/myother.txt", // Not permitted yet
|
||||
//"other/../my//path/../path/to/otherother.txt", // Not permitted yet
|
||||
//"new\nnew.txt", // \n not permitted
|
||||
//"two\n", // \n not permitted (yet)
|
||||
//"four\U0010FFFF", // Illegal byte sequence. git won't accept.
|
||||
} {
|
||||
phase(fmt.Sprintf("Mallory tries %02d: %q", i, name))
|
||||
contents := "the name of this file is the talking heads... i mean, " + name
|
||||
makeFile(t, name, contents)
|
||||
assertFileExists(t, name)
|
||||
assertFileMissing(t, name+".gpg")
|
||||
assertFileContents(t, name, contents)
|
||||
|
||||
runBB(t, "file", "add", name)
|
||||
assertFileMissing(t, name)
|
||||
assertFileExists(t, name+".gpg")
|
||||
|
||||
runBB(t, "decrypt", name)
|
||||
assertFileExists(t, name)
|
||||
assertFileExists(t, name+".gpg")
|
||||
assertFileContents(t, name, contents)
|
||||
|
||||
runBB(t, "encrypt", name)
|
||||
assertFileExists(t, name)
|
||||
assertFileExists(t, name+".gpg")
|
||||
assertFileContents(t, name, contents)
|
||||
|
||||
runBB(t, "shred", name)
|
||||
assertFileMissing(t, name)
|
||||
assertFileExists(t, name+".gpg")
|
||||
}
|
||||
}
|
||||
|
||||
// More tests to implement.
|
||||
// 1. Verify that the --gid works (blackbox decrypt --gid)
|
||||
617
integrationTest/ithelpers.go
Normal file
617
integrationTest/ithelpers.go
Normal file
@@ -0,0 +1,617 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/StackExchange/blackbox/v2/pkg/bblog"
|
||||
"github.com/StackExchange/blackbox/v2/pkg/bbutil"
|
||||
"github.com/StackExchange/blackbox/v2/pkg/vcs"
|
||||
_ "github.com/StackExchange/blackbox/v2/pkg/vcs/_all"
|
||||
|
||||
"github.com/andreyvit/diff"
|
||||
)
|
||||
|
||||
var verbose = flag.Bool("verbose", false, "reveal stderr")
|
||||
var nocleanup = flag.Bool("nocleanup", false, "do not delete the tmp directory")
|
||||
|
||||
type userinfo struct {
|
||||
name string
|
||||
dir string // .gnupg-$name
|
||||
agentInfo string // GPG_AGENT_INFO
|
||||
email string
|
||||
fullname string
|
||||
}
|
||||
|
||||
var users = map[string]*userinfo{}
|
||||
|
||||
func init() {
|
||||
testing.Init()
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
var logErr *log.Logger
|
||||
var logDebug *log.Logger
|
||||
|
||||
func init() {
|
||||
logErr = bblog.GetErr()
|
||||
logDebug = bblog.GetDebug(*verbose)
|
||||
}
|
||||
|
||||
func getVcs(t *testing.T, name string) vcs.Vcs {
|
||||
t.Helper()
|
||||
// Set up the vcs
|
||||
for _, v := range vcs.Catalog {
|
||||
logDebug.Printf("Testing vcs: %v == %v", name, v.Name)
|
||||
if strings.ToLower(v.Name) == strings.ToLower(name) {
|
||||
h, err := v.New()
|
||||
if err != nil {
|
||||
return nil // No idea how that would happen.
|
||||
}
|
||||
return h
|
||||
}
|
||||
logDebug.Println("...Nope.")
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestBasicCommands's helpers
|
||||
|
||||
func makeHomeDir(t *testing.T, testname string) {
|
||||
t.Helper()
|
||||
var homedir string
|
||||
var err error
|
||||
|
||||
if *nocleanup {
|
||||
// Make a predictable location; don't deleted.
|
||||
homedir = "/tmp/bbhome-" + testname
|
||||
os.RemoveAll(homedir)
|
||||
err = os.Mkdir(homedir, 0770)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("mk-home %q: %v", homedir, err))
|
||||
}
|
||||
} else {
|
||||
// Make a random location that is deleted automatically
|
||||
homedir, err = ioutil.TempDir("", filepath.Join("bbhome-"+testname))
|
||||
defer os.RemoveAll(homedir) // clean up
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Setenv("HOME", homedir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logDebug.Printf("TESTING DIR HOME: cd %v\n", homedir)
|
||||
|
||||
repodir := filepath.Join(homedir, "repo")
|
||||
err = os.Mkdir(repodir, 0770)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("mk-repo %q: %v", repodir, err))
|
||||
}
|
||||
err = os.Chdir(repodir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func createDummyFilesAdmin(t *testing.T) {
|
||||
// This creates a repo with real data, except any .gpg file
|
||||
// is just garbage.
|
||||
addLineSorted(t, ".blackbox/blackbox-admins.txt", "user1@example.com")
|
||||
addLineSorted(t, ".blackbox/blackbox-admins.txt", "user2@example.com")
|
||||
addLineSorted(t, ".blackbox/blackbox-files.txt", "foo.txt")
|
||||
addLineSorted(t, ".blackbox/blackbox-files.txt", "bar.txt")
|
||||
makeFile(t, "foo.txt", "I am the foo.txt file!")
|
||||
makeFile(t, "bar.txt", "I am the foo.txt file!")
|
||||
makeFile(t, "foo.txt.gpg", "V nz gur sbb.gkg svyr!")
|
||||
makeFile(t, "bar.txt.gpg", "V nz gur one.gkg svyr!")
|
||||
}
|
||||
|
||||
func createFilesStatus(t *testing.T) {
|
||||
// This creates a few files with real plaintext but fake cyphertext.
|
||||
// There are a variety of timestamps to enable many statuses.
|
||||
t.Helper()
|
||||
|
||||
// DECRYPTED: File is decrypted and ready to edit (unknown if it has been edited).
|
||||
// ENCRYPTED: GPG file is newer than plaintext. Indicates recented edited then encrypted.
|
||||
// SHREDDED: Plaintext is missing.
|
||||
// GPGMISSING: The .gpg file is missing. Oops?
|
||||
// PLAINERROR: Can't access the plaintext file to determine status.
|
||||
// GPGERROR: Can't access .gpg file to determine status.
|
||||
|
||||
addLineSorted(t, ".blackbox/blackbox-files.txt", "status-DECRYPTED.txt")
|
||||
addLineSorted(t, ".blackbox/blackbox-files.txt", "status-ENCRYPTED.txt")
|
||||
addLineSorted(t, ".blackbox/blackbox-files.txt", "status-SHREDDED.txt")
|
||||
addLineSorted(t, ".blackbox/blackbox-files.txt", "status-GPGMISSING.txt")
|
||||
// addLineSorted(t, ".blackbox/blackbox-files.txt", "status-PLAINERROR.txt")
|
||||
// addLineSorted(t, ".blackbox/blackbox-files.txt", "status-GPGERROR.txt")
|
||||
addLineSorted(t, ".blackbox/blackbox-files.txt", "status-BOTHMISSING.txt")
|
||||
|
||||
// Combination of age difference either missing, file error, both missing.
|
||||
makeFile(t, "status-DECRYPTED.txt", "File with DECRYPTED in it.")
|
||||
makeFile(t, "status-DECRYPTED.txt.gpg", "Svyr jvgu QRPELCGRQ va vg.")
|
||||
|
||||
makeFile(t, "status-ENCRYPTED.txt", "File with ENCRYPTED in it.")
|
||||
makeFile(t, "status-ENCRYPTED.txt.gpg", "Svyr jvgu RAPELCGRQ va vg.")
|
||||
|
||||
// Plaintext intentionally missing.
|
||||
makeFile(t, "status-SHREDDED.txt.gpg", "Svyr jvgu FUERQQRQ va vg.")
|
||||
|
||||
makeFile(t, "status-GPGMISSING.txt", "File with GPGMISSING in it.")
|
||||
// gpg file intentionally missing.
|
||||
|
||||
// Plaintext intentionally missing. ("status-BOTHMISSING.txt")
|
||||
// gpg file intentionally missing. ("status-BOTHMISSING.txt.gpg")
|
||||
|
||||
// NB(tlim): commented out. I can't think of an error I can reproduce.
|
||||
// makeFile(t, "status-PLAINERROR.txt", "File with PLAINERROR in it.")
|
||||
// makeFile(t, "status-PLAINERROR.txt.gpg", "Svyr jvgu CYNVAREEBE va vg.")
|
||||
// setFilePerms(t, "status-PLAINERROR.txt", 0000)
|
||||
|
||||
// NB(tlim): commented out. I can't think of an error I can reproduce.
|
||||
// makeFile(t, "status-GPGERROR.txt", "File with GPGERROR in it.")
|
||||
// makeFile(t, "status-GPGERROR.txt.gpg", "Svyr jvgu TCTREEBE va vg.")
|
||||
// setFilePerms(t, "status-GPGERROR.txt.gpg", 0000)
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
if err := bbutil.Touch("status-DECRYPTED.txt"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := bbutil.Touch("status-ENCRYPTED.txt.gpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func addLineSorted(t *testing.T, filename, line string) {
|
||||
err := bbutil.AddLinesToSortedFile(filename, line)
|
||||
if err != nil {
|
||||
t.Fatalf("addLineSorted failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func removeFile(t *testing.T, name string) {
|
||||
os.RemoveAll(name)
|
||||
}
|
||||
|
||||
func makeFile(t *testing.T, name string, content string) {
|
||||
t.Helper()
|
||||
|
||||
err := ioutil.WriteFile(name, []byte(content), 0666)
|
||||
if err != nil {
|
||||
t.Fatalf("makeFile can't create %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func setFilePerms(t *testing.T, name string, perms int) {
|
||||
t.Helper()
|
||||
|
||||
err := os.Chmod(name, os.FileMode(perms))
|
||||
if err != nil {
|
||||
t.Fatalf("setFilePerms can't chmod %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
var originPath string // CWD when program started.
|
||||
|
||||
// checkOutput runs blackbox with args, the last arg is the filename
|
||||
// of the expected output. Error if output is not expected.
|
||||
func checkOutput(name string, t *testing.T, args ...string) {
|
||||
t.Helper()
|
||||
|
||||
cmd := exec.Command(PathToBlackBox(), args...)
|
||||
cmd.Stdin = nil
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = os.Stderr
|
||||
var gb []byte
|
||||
gb, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("checkOutput(%q): %w", args, err))
|
||||
}
|
||||
got := string(gb)
|
||||
|
||||
wb, err := ioutil.ReadFile(filepath.Join(originPath, "test_data", name))
|
||||
if err != nil {
|
||||
t.Fatalf("checkOutput can't read %v: %v", name, err)
|
||||
}
|
||||
want := string(wb)
|
||||
|
||||
//fmt.Printf("CHECKOUTPUT g: %v\n", got)
|
||||
//fmt.Printf("CHECKOUTPUT w: %v\n", want)
|
||||
|
||||
if g, w := got, want; g != w {
|
||||
t.Errorf("checkOutput(%q) mismatch (-got +want):\n%s",
|
||||
args, diff.LineDiff(g, w))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func invalidArgs(t *testing.T, args ...string) {
|
||||
t.Helper()
|
||||
|
||||
logDebug.Printf("invalidArgs(%q): \n", args)
|
||||
cmd := exec.Command(PathToBlackBox(), args...)
|
||||
cmd.Stdin = nil
|
||||
if *verbose {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
err := cmd.Run()
|
||||
if err == nil {
|
||||
logDebug.Println("BAD")
|
||||
t.Fatal(fmt.Errorf("invalidArgs(%q): wanted failure but got success", args))
|
||||
}
|
||||
logDebug.Printf("^^^^ (correct error received): err=%q\n", err)
|
||||
}
|
||||
|
||||
// TestAliceAndBob's helpers.
|
||||
|
||||
func setupUser(t *testing.T, user, passphrase string) {
|
||||
t.Helper()
|
||||
logDebug.Printf("DEBUG: setupUser %q %q\n", user, passphrase)
|
||||
}
|
||||
|
||||
var pathToBlackBox string
|
||||
|
||||
// PathToBlackBox returns the path to the executable we compile for integration testing.
|
||||
func PathToBlackBox() string { return pathToBlackBox }
|
||||
|
||||
// SetPathToBlackBox sets the path.
|
||||
func SetPathToBlackBox(n string) {
|
||||
logDebug.Printf("PathToBlackBox=%q\n", n)
|
||||
pathToBlackBox = n
|
||||
}
|
||||
|
||||
func runBB(t *testing.T, args ...string) {
|
||||
t.Helper()
|
||||
|
||||
logDebug.Printf("runBB(%q)\n", args)
|
||||
cmd := exec.Command(PathToBlackBox(), args...)
|
||||
cmd.Stdin = nil
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("runBB(%q): %w", args, err))
|
||||
}
|
||||
}
|
||||
|
||||
func phase(msg string) {
|
||||
logDebug.Println("********************")
|
||||
logDebug.Println("********************")
|
||||
logDebug.Printf("********* %v\n", msg)
|
||||
logDebug.Println("********************")
|
||||
logDebug.Println("********************")
|
||||
}
|
||||
|
||||
func makeAdmin(t *testing.T, name, fullname, email string) string {
|
||||
testing.Init()
|
||||
|
||||
dir, err := filepath.Abs(filepath.Join(os.Getenv("HOME"), ".gnupg-"+name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Mkdir(dir, 0700)
|
||||
|
||||
u := &userinfo{
|
||||
name: name,
|
||||
dir: dir,
|
||||
fullname: fullname,
|
||||
email: email,
|
||||
}
|
||||
users[name] = u
|
||||
|
||||
// GNUPGHOME=u.dir
|
||||
// echo 'pinentry-program' "$(which pinentry-tty)" >> "$GNUPGHOME/gpg-agent.conf"
|
||||
os.Setenv("GNUPGHOME", u.dir)
|
||||
if runtime.GOOS != "darwin" {
|
||||
ai, err := bbutil.RunBashOutput("gpg-agent", "--homedir", u.dir, "--daemon")
|
||||
// NB(tlim): It should return something like:
|
||||
// `GPG_AGENT_INFO=/home/tlimoncelli/.gnupg/S.gpg-agent:18548:1; export GPG_AGENT_INFO;`
|
||||
if err != nil {
|
||||
//t.Fatal(err)
|
||||
}
|
||||
if !strings.HasPrefix(ai, "GPG_AGENT_INFO=") {
|
||||
fmt.Println("WARNING: gpg-agent didn't output what we expected. Assumed dead.")
|
||||
} else {
|
||||
u.agentInfo = ai[15:strings.Index(ai, ";")]
|
||||
os.Setenv("GPG_AGENT_INFO", u.agentInfo)
|
||||
fmt.Printf("GPG_AGENT_INFO=%q (was %q)\n", ai, u.agentInfo)
|
||||
}
|
||||
}
|
||||
|
||||
os.Setenv("GNUPGHOME", u.dir)
|
||||
// Generate key:
|
||||
if hasQuick(t) {
|
||||
fmt.Println("DISCOVERED: NEW GPG")
|
||||
fmt.Printf("Generating %q using --qgk\n", u.email)
|
||||
bbutil.RunBash("gpg",
|
||||
"--homedir", u.dir,
|
||||
"--batch",
|
||||
"--passphrase", "",
|
||||
"--quick-generate-key", u.email,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
fmt.Println("DISCOVERED: OLD GPG")
|
||||
fmt.Println("MAKING KEY")
|
||||
|
||||
tmpfile, err := ioutil.TempFile("", "example")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
batch := `%echo Generating a basic OpenPGP key
|
||||
Key-Type: RSA
|
||||
Key-Length: 2048
|
||||
Subkey-Type: RSA
|
||||
Subkey-Length: 2048
|
||||
Name-Real: ` + u.fullname + `
|
||||
Name-Comment: Not for actual use
|
||||
Name-Email: ` + u.email + `
|
||||
Expire-Date: 0
|
||||
%pubring ` + filepath.Join(u.dir, `pubring.gpg`) + `
|
||||
%secring ` + filepath.Join(u.dir, `secring.gpg`) + `
|
||||
# Do a commit here, so that we can later print "done"
|
||||
%commit
|
||||
%echo done`
|
||||
//fmt.Printf("BATCH START\n%s\nBATCH END\n", batch)
|
||||
fmt.Fprintln(tmpfile, batch)
|
||||
|
||||
// FIXME(tlim): The batch file should include a password, but then
|
||||
// we need to figure out how to get "blackbox encrypt" and other
|
||||
// commands to input a password in an automated way.
|
||||
// To experiment with this, add after "Expire-Date:" a line like:
|
||||
// Passphrase: kljfhslfjkhsaljkhsdflgjkhsd
|
||||
// Current status: without that line GPG keys have no passphrase
|
||||
// and none is requested.
|
||||
|
||||
bbutil.RunBash("gpg",
|
||||
"--homedir", u.dir,
|
||||
"--verbose",
|
||||
"--batch",
|
||||
"--gen-key",
|
||||
tmpfile.Name(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// We do this just to for gpg to create trustdb.gpg
|
||||
bbutil.RunBash("gpg",
|
||||
"--homedir", u.dir,
|
||||
"--list-keys",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bbutil.RunBash("gpg",
|
||||
"--homedir", u.dir,
|
||||
"--list-secret-keys",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return u.dir
|
||||
}
|
||||
|
||||
func hasQuick(t *testing.T) bool {
|
||||
testing.Init()
|
||||
fmt.Println("========== Do we have --quick-generate-key?")
|
||||
err := bbutil.RunBash("gpg2",
|
||||
"--dry-run",
|
||||
"--quick-generate-key",
|
||||
"--batch",
|
||||
"--passphrase", "",
|
||||
"foo", "rsa", "encr")
|
||||
fmt.Println("========== Done")
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
//fmt.Printf("DISCOVER GPG: %d", err.ExitCode())
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
if exitError.ExitCode() == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func become(t *testing.T, name string) {
|
||||
testing.Init()
|
||||
u := users[name]
|
||||
|
||||
os.Setenv("GNUPGHOME", u.dir)
|
||||
os.Setenv("GPG_AGENT_INFO", u.agentInfo)
|
||||
bbutil.RunBash("git", "config", "user.name", u.name)
|
||||
bbutil.RunBash("git", "config", "user.email", u.fullname)
|
||||
}
|
||||
|
||||
// // Get fingerprint:
|
||||
// // Retrieve fingerprint of generated key.
|
||||
// // Use it to extract the secret/public keys.
|
||||
// // (stolen from https://raymii.org/s/articles/GPG_noninteractive_batch_sign_trust_and_send_gnupg_keys.html)
|
||||
//
|
||||
// // fpr=`gpg --homedir /tmp/blackbox_createrole --fingerprint --with-colons "$ROLE_NAME" | awk -F: '/fpr:/ {print $10}' | head -n 1`
|
||||
// var fpr string
|
||||
// bbutil.RunBashOutput("gpg",
|
||||
// "--homedir", "/tmp/blackbox_createrole",
|
||||
// "--fingerprint",
|
||||
// "--with-colons",
|
||||
// u.email,
|
||||
// )
|
||||
// for i, l := range string.Split(out, "\n") {
|
||||
// if string.HasPrefix(l, "fpr:") {
|
||||
// fpr = strings.Split(l, ":")[9]
|
||||
// }
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// // Create key key:
|
||||
// // gpg --homedir "$gpghomedir" --batch --passphrase '' --quick-add-key "$fpr" rsa encr
|
||||
// bbutil.RunBash("gpg",
|
||||
// "--homedir", u.dir,
|
||||
// "--batch",
|
||||
// "--passphrase", "",
|
||||
// "--quick-add-key", fpr,
|
||||
// "rsa", "encr",
|
||||
// )
|
||||
|
||||
// function md5sum_file() {
|
||||
// # Portably generate the MD5 hash of file $1.
|
||||
// case $(uname -s) in
|
||||
// Darwin | FreeBSD )
|
||||
// md5 -r "$1" | awk '{ print $1 }'
|
||||
// ;;
|
||||
// NetBSD )
|
||||
// md5 -q "$1"
|
||||
// ;;
|
||||
// SunOS )
|
||||
// digest -a md5 "$1"
|
||||
// ;;
|
||||
// Linux )
|
||||
// md5sum "$1" | awk '{ print $1 }'
|
||||
// ;;
|
||||
// CYGWIN* )
|
||||
// md5sum "$1" | awk '{ print $1 }'
|
||||
// ;;
|
||||
// * )
|
||||
// echo 'ERROR: Unknown OS. Exiting.'
|
||||
// exit 1
|
||||
// ;;
|
||||
// esac
|
||||
// }
|
||||
//
|
||||
// function assert_file_missing() {
|
||||
// if [[ -e "$1" ]]; then
|
||||
// echo "ASSERT FAILED: ${1} should not exist."
|
||||
// exit 1
|
||||
// fi
|
||||
// }
|
||||
//
|
||||
// function assert_file_exists() {
|
||||
// if [[ ! -e "$1" ]]; then
|
||||
// echo "ASSERT FAILED: ${1} should exist."
|
||||
// echo "PWD=$(/usr/bin/env pwd -P)"
|
||||
// #echo "LS START"
|
||||
// #ls -la
|
||||
// #echo "LS END"
|
||||
// exit 1
|
||||
// fi
|
||||
// }
|
||||
// function assert_file_md5hash() {
|
||||
// local file="$1"
|
||||
// local wanted="$2"
|
||||
// assert_file_exists "$file"
|
||||
// local found
|
||||
// found=$(md5sum_file "$file")
|
||||
// if [[ "$wanted" != "$found" ]]; then
|
||||
// echo "ASSERT FAILED: $file hash wanted=$wanted found=$found"
|
||||
// exit 1
|
||||
// fi
|
||||
// }
|
||||
// function assert_file_group() {
|
||||
// local file="$1"
|
||||
// local wanted="$2"
|
||||
// local found
|
||||
// assert_file_exists "$file"
|
||||
//
|
||||
// case $(uname -s) in
|
||||
// Darwin | FreeBSD | NetBSD )
|
||||
// found=$(stat -f '%Dg' "$file")
|
||||
// ;;
|
||||
// Linux | SunOS )
|
||||
// found=$(stat -c '%g' "$file")
|
||||
// ;;
|
||||
// CYGWIN* )
|
||||
// echo "ASSERT_FILE_GROUP: Running on Cygwin. Not being tested."
|
||||
// return 0
|
||||
// ;;
|
||||
// * )
|
||||
// echo 'ERROR: Unknown OS. Exiting.'
|
||||
// exit 1
|
||||
// ;;
|
||||
// esac
|
||||
//
|
||||
// echo "DEBUG: assert_file_group X${wanted}X vs. X${found}X"
|
||||
// echo "DEBUG:" $(which stat)
|
||||
// if [[ "$wanted" != "$found" ]]; then
|
||||
// echo "ASSERT FAILED: $file chgrp group wanted=$wanted found=$found"
|
||||
// exit 1
|
||||
// fi
|
||||
// }
|
||||
// function assert_file_perm() {
|
||||
// local wanted="$1"
|
||||
// local file="$2"
|
||||
// local found
|
||||
// assert_file_exists "$file"
|
||||
//
|
||||
// case $(uname -s) in
|
||||
// Darwin | FreeBSD | NetBSD )
|
||||
// found=$(stat -f '%Sp' "$file")
|
||||
// ;;
|
||||
// # NB(tlim): CYGWIN hasn't been tested. It might be more like Darwin.
|
||||
// Linux | CYGWIN* | SunOS )
|
||||
// found=$(stat -c '%A' "$file")
|
||||
// ;;
|
||||
// * )
|
||||
// echo 'ERROR: Unknown OS. Exiting.'
|
||||
// exit 1
|
||||
// ;;
|
||||
// esac
|
||||
//
|
||||
// echo "DEBUG: assert_file_perm X${wanted}X vs. X${found}X"
|
||||
// echo "DEBUG:" $(which stat)
|
||||
// if [[ "$wanted" != "$found" ]]; then
|
||||
// echo "ASSERT FAILED: $file chgrp perm wanted=$wanted found=$found"
|
||||
// exit 1
|
||||
// fi
|
||||
// }
|
||||
// function assert_line_not_exists() {
|
||||
// local target="$1"
|
||||
// local file="$2"
|
||||
// assert_file_exists "$file"
|
||||
// if grep -F -x -s -q >/dev/null "$target" "$file" ; then
|
||||
// echo "ASSERT FAILED: line '$target' should not exist in file $file"
|
||||
// echo "==== file contents: START $file"
|
||||
// cat "$file"
|
||||
// echo "==== file contents: END $file"
|
||||
// exit 1
|
||||
// fi
|
||||
// }
|
||||
// function assert_line_exists() {
|
||||
// local target="$1"
|
||||
// local file="$2"
|
||||
// assert_file_exists "$file"
|
||||
// if ! grep -F -x -s -q >/dev/null "$target" "$file" ; then
|
||||
// echo "ASSERT FAILED: line '$target' should exist in file $file"
|
||||
// echo "==== file contents: START $file"
|
||||
// cat "$file"
|
||||
// echo "==== file contents: END $file"
|
||||
// exit 1
|
||||
// fi
|
||||
// }
|
||||
2
integrationTest/test_data/000-admin-list.txt
Normal file
2
integrationTest/test_data/000-admin-list.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
user1@example.com
|
||||
user2@example.com
|
||||
2
integrationTest/test_data/000-file-list.txt
Normal file
2
integrationTest/test_data/000-file-list.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
bar.txt
|
||||
foo.txt
|
||||
9
integrationTest/test_data/000-status.txt
Normal file
9
integrationTest/test_data/000-status.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
+-------------+------------------------+
|
||||
| STATUS | NAME |
|
||||
+-------------+------------------------+
|
||||
| BOTHMISSING | status-BOTHMISSING.txt |
|
||||
| DECRYPTED | status-DECRYPTED.txt |
|
||||
| ENCRYPTED | status-ENCRYPTED.txt |
|
||||
| GPGMISSING | status-GPGMISSING.txt |
|
||||
| SHREDDED | status-SHREDDED.txt |
|
||||
+-------------+------------------------+
|
||||
1
integrationTest/test_data/alice-cat-plain.txt
Normal file
1
integrationTest/test_data/alice-cat-plain.txt
Normal file
@@ -0,0 +1 @@
|
||||
I am the foo.txt file!
|
||||
5
integrationTest/test_data/basic-status.txt
Normal file
5
integrationTest/test_data/basic-status.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
+-----------+---------+
|
||||
| STATUS | NAME |
|
||||
+-----------+---------+
|
||||
| ENCRYPTED | foo.txt |
|
||||
+-----------+---------+
|
||||
1
integrationTest/test_data/reencrypt-plain.txt
Normal file
1
integrationTest/test_data/reencrypt-plain.txt
Normal file
@@ -0,0 +1 @@
|
||||
I am the foo.txt file!
|
||||
6
integrationTest/test_data/status-noreg.txt
Normal file
6
integrationTest/test_data/status-noreg.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
+-----------+----------------------+
|
||||
| STATUS | NAME |
|
||||
+-----------+----------------------+
|
||||
| ENCRYPTED | status-ENCRYPTED.txt |
|
||||
| NOTREG | blah.txt |
|
||||
+-----------+----------------------+
|
||||
Reference in New Issue
Block a user