I hate commit messages
This commit is contained in:
1 parent
209af0d820
commit
8654c45e35
11 files changed
+278
-35
No files matched your search
@@ -78,6 +78,7 @@ func (this *ContractsGenerator) createPublicContractWithGoodDefaultValues(key *k
|
|||||||
this.privateContract.AcceptedSignatureScheme = []string{"rsa_pss"}
|
this.privateContract.AcceptedSignatureScheme = []string{"rsa_pss"}
|
||||||
this.privateContract.AcceptedBlockCypherModes = []string{"ofb"}
|
this.privateContract.AcceptedBlockCypherModes = []string{"ofb"}
|
||||||
this.privateContract.AcceptedBlockCyphers = []string{"aes"}
|
this.privateContract.AcceptedBlockCyphers = []string{"aes"}
|
||||||
|
this.privateContract.AcceptedHashes = []string{"sha384", "sha512", "sha256"}
|
||||||
this.privateContract.RSAPublicKey = []*keys.RSAPublicKey{key}
|
this.privateContract.RSAPublicKey = []*keys.RSAPublicKey{key}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/filou/contract"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fileExists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileContract(path string) (*contract.PublicContract, error) {
|
||||||
|
ret := new(contract.PublicContract)
|
||||||
|
if data, err := ioutil.ReadFile(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if err = json.Unmarshal(data, ret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetContract(location string) (*contract.PublicContract, error) {
|
||||||
|
switch {
|
||||||
|
case fileExists(location):
|
||||||
|
return getFileContract(location)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("could not find public contract at " + location)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/filou/contract"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getMockContract() *contract.PublicContract {
|
||||||
|
c := new(contract.PublicContract)
|
||||||
|
c.AcceptedHashes = []string{"hash_function_1", "hash_function_2"}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMockContractAndFile() *contract.PublicContract {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFileContract(t *testing.T) {
|
||||||
|
}
|
||||||
@@ -1,3 +1,39 @@
|
|||||||
package files
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rsa"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/didiercrunch/filou/contract"
|
||||||
|
"github.com/didiercrunch/filou/helper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getRSAPrivateKey() contract.GetRSAPrivateKey {
|
||||||
|
return func() (*rsa.PrivateKey, error) {
|
||||||
|
return helper.GetRSAPrivateKey(2048), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSensibleAcceptedContract() *contract.AcceptedContract {
|
||||||
|
ret := new(contract.AcceptedContract)
|
||||||
|
ret.Hash = "sha512"
|
||||||
|
ret.BlockCipher = "aes256"
|
||||||
|
ret.BlockCipherMode = "ofb"
|
||||||
|
ret.AsynchronousEncryptionScheme = "rsa_oaep"
|
||||||
|
ret.SignatureScheme = "rsa_pss"
|
||||||
|
ret.RsaPrivateKey = getRSAPrivateKey()
|
||||||
|
ret.RsaPublicKey = helper.GetRSAPublicKey(2048)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncryptFile(t *testing.T) {
|
||||||
|
buffer := bytes.NewBufferString("some secret text")
|
||||||
|
w := helper.NewMockIoWriter()
|
||||||
|
c := getSensibleAcceptedContract()
|
||||||
|
if err := EncryptFile(buffer, w, c); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
fmt.Println(w.String())
|
||||||
|
}
|
||||||
+57
-4
@@ -1,15 +1,14 @@
|
|||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/didiercrunch/filou/contract"
|
"github.com/didiercrunch/filou/contract"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = fmt.Print
|
|
||||||
|
|
||||||
type encryptor func(reader io.Reader, writer io.Writer, acceptedContract *contract.AcceptedContract) error
|
type encryptor func(reader io.Reader, writer io.Writer, acceptedContract *contract.AcceptedContract) error
|
||||||
|
|
||||||
type PathEncryptor struct {
|
type PathEncryptor struct {
|
||||||
@@ -17,7 +16,61 @@ type PathEncryptor struct {
|
|||||||
EncryptDirectory encryptor
|
EncryptDirectory encryptor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *PathEncryptor) EncryptPath(path string, writer io.Writer, acceptedContract *contract.AcceptedContract) error {
|
func (this *PathEncryptor) fileExists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PathEncryptor) getDefaultPathToEncryptFile(originalFileName string) string {
|
||||||
|
isFileWithSingleExtension := regexp.MustCompile(`\.\w+$`)
|
||||||
|
isFileWithDoubleExtension := regexp.MustCompile(`\.\w+\.\w+$`)
|
||||||
|
isHiddenDirectory := regexp.MustCompile(`\/.\w+$`)
|
||||||
|
isHiddenFile := regexp.MustCompile(`\/\.\w+\.\w+$`)
|
||||||
|
|
||||||
|
if isHiddenFile.MatchString(originalFileName) {
|
||||||
|
return isFileWithSingleExtension.ReplaceAllString(originalFileName, ".filou")
|
||||||
|
}
|
||||||
|
if isHiddenDirectory.MatchString(originalFileName) {
|
||||||
|
return originalFileName + ".filou"
|
||||||
|
}
|
||||||
|
if isFileWithDoubleExtension.MatchString(originalFileName) {
|
||||||
|
return isFileWithDoubleExtension.ReplaceAllString(originalFileName, ".filou")
|
||||||
|
}
|
||||||
|
if isFileWithSingleExtension.MatchString(originalFileName) {
|
||||||
|
return isFileWithSingleExtension.ReplaceAllString(originalFileName, ".filou")
|
||||||
|
}
|
||||||
|
return originalFileName + ".filou"
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a file with permission set to 600
|
||||||
|
func (this *PathEncryptor) createPrivateFile(name string) (*os.File, error) {
|
||||||
|
return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PathEncryptor) EncryptPathToDefaultFile(path string, acceptedContract *contract.AcceptedContract) error {
|
||||||
|
outputPathName := this.getDefaultPathToEncryptFile(path)
|
||||||
|
if _, err := os.Stat(outputPathName); !os.IsNotExist(err) {
|
||||||
|
return errors.New("the default path to encrypt already exists.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PathEncryptor) EncryptPathToOtherPath(fromPath, toPath string, acceptedContract *contract.AcceptedContract) error {
|
||||||
|
if this.fileExists(toPath) {
|
||||||
|
return errors.New("output file already exists")
|
||||||
|
}
|
||||||
|
if f, err := this.createPrivateFile(toPath); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
defer f.Close()
|
||||||
|
return this.EncryptPathToWriter(fromPath, f, acceptedContract)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PathEncryptor) EncryptPathToWriter(path string, writer io.Writer, acceptedContract *contract.AcceptedContract) error {
|
||||||
|
if !this.fileExists(path) {
|
||||||
|
return errors.New("input file does not exists")
|
||||||
|
}
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+47
-10
@@ -3,6 +3,7 @@ package files
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/didiercrunch/filou/contract"
|
"github.com/didiercrunch/filou/contract"
|
||||||
@@ -20,11 +21,16 @@ func (this *mockEncryptor) Encrypt(reader io.Reader, writer io.Writer, acceptedC
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeFile(name string, t *testing.T) {
|
||||||
|
if err := os.Remove(name); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestEncryptPathWrongPath(t *testing.T) {
|
func TestEncryptPathWrongPath(t *testing.T) {
|
||||||
pe := new(PathEncryptor)
|
pe := new(PathEncryptor)
|
||||||
|
|
||||||
filePath := "/something_that_does_not_exists"
|
filePath := "/something_that_does_not_exists"
|
||||||
if err := pe.EncryptPath(filePath, nil, nil); err == nil {
|
if err := pe.EncryptPathToWriter(filePath, nil, nil); err == nil {
|
||||||
t.Error("should say the file does not exists")
|
t.Error("should say the file does not exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +40,7 @@ func TestEncryptPathForDirectory(t *testing.T) {
|
|||||||
fileEncryptor, dirEncryptor := new(mockEncryptor), new(mockEncryptor)
|
fileEncryptor, dirEncryptor := new(mockEncryptor), new(mockEncryptor)
|
||||||
pe := &PathEncryptor{fileEncryptor.Encrypt, dirEncryptor.Encrypt}
|
pe := &PathEncryptor{fileEncryptor.Encrypt, dirEncryptor.Encrypt}
|
||||||
|
|
||||||
if err := pe.EncryptPath(dirName, nil, nil); err != nil {
|
if err := pe.EncryptPathToWriter(dirName, nil, nil); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else if fileEncryptor.N != 0 {
|
} else if fileEncryptor.N != 0 {
|
||||||
t.Error("should have 0", fileEncryptor.N)
|
t.Error("should have 0", fileEncryptor.N)
|
||||||
@@ -43,17 +49,48 @@ func TestEncryptPathForDirectory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncryptPathForFile(t *testing.T) {
|
func TestEncryptPathToWriter(t *testing.T) {
|
||||||
file := helper.GetTmpEmptyFile()
|
file := helper.GetTmpFileWithText("foo bar")
|
||||||
defer func() {
|
defer removeFile(file.Name(), t)
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
fileEncryptor, dirEncryptor := new(mockEncryptor), new(mockEncryptor)
|
fileEncryptor, dirEncryptor := new(mockEncryptor), new(mockEncryptor)
|
||||||
pe := &PathEncryptor{fileEncryptor.Encrypt, dirEncryptor.Encrypt}
|
pe := &PathEncryptor{fileEncryptor.Encrypt, dirEncryptor.Encrypt}
|
||||||
|
|
||||||
if err := pe.EncryptPath(file.Name(), nil, nil); err != nil {
|
if err := pe.EncryptPathToWriter(file.Name(), nil, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if fileEncryptor.N != 1 {
|
||||||
|
t.Error("should have 1", fileEncryptor.N)
|
||||||
|
} else if dirEncryptor.N != 0 {
|
||||||
|
t.Error("should have 0", dirEncryptor.N)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDefaultPathToEncryptFile(t *testing.T) {
|
||||||
|
cases := map[string]string{
|
||||||
|
"/foo/bar.txt": "/foo/bar.filou", // regular file
|
||||||
|
"/foo/bar.tar.gz": "/foo/bar.filou", // file with two extensions
|
||||||
|
"/foo/bar": "/foo/bar.filou", // directory
|
||||||
|
"/foo/.bar": "/foo/.bar.filou", // hidden directory
|
||||||
|
"/foo/.bar.txt": "/foo/.bar.filou", // hidden file
|
||||||
|
}
|
||||||
|
pe := new(PathEncryptor)
|
||||||
|
for input, expectedOutput := range cases {
|
||||||
|
if output := pe.getDefaultPathToEncryptFile(input); output != expectedOutput {
|
||||||
|
t.Error("expected", expectedOutput, "but received", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncryptPathToOtherPath(t *testing.T) {
|
||||||
|
fromFile := helper.GetTmpFileWithText("foo bar")
|
||||||
|
defer removeFile(fromFile.Name(), t)
|
||||||
|
|
||||||
|
toFile := helper.GetTmpFileName()
|
||||||
|
defer removeFile(toFile, t)
|
||||||
|
|
||||||
|
fileEncryptor, dirEncryptor := new(mockEncryptor), new(mockEncryptor)
|
||||||
|
pe := &PathEncryptor{fileEncryptor.Encrypt, dirEncryptor.Encrypt}
|
||||||
|
|
||||||
|
if err := pe.EncryptPathToOtherPath(fromFile.Name(), toFile, nil); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else if fileEncryptor.N != 1 {
|
} else if fileEncryptor.N != 1 {
|
||||||
t.Error("should have 1", fileEncryptor.N)
|
t.Error("should have 1", fileEncryptor.N)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
@@ -123,6 +124,8 @@ func GetRSAPrivateKey(size int) *rsa.PrivateKey {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return a tmp file in the default tmp directory. If fails, the function
|
||||||
|
// will panic
|
||||||
func GetTmpEmptyFile() *os.File {
|
func GetTmpEmptyFile() *os.File {
|
||||||
if f, err := ioutil.TempFile("", ""); err != nil {
|
if f, err := ioutil.TempFile("", ""); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -131,6 +134,32 @@ func GetTmpEmptyFile() *os.File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returned a closed tmp file with text in it.
|
||||||
|
func GetTmpFileWithText(text string) *os.File {
|
||||||
|
f := GetTmpEmptyFile()
|
||||||
|
defer func() {
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Fprint(f, text)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a path that can be safely use as tmp file
|
||||||
|
func GetTmpFileName() string {
|
||||||
|
f := GetTmpEmptyFile()
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := os.Remove(f.Name()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a tmp dir and return its path. If failing, the method will panic
|
||||||
func GetTmpEmptyDir() string {
|
func GetTmpEmptyDir() string {
|
||||||
if dirName, err := ioutil.TempDir("", ""); err != nil {
|
if dirName, err := ioutil.TempDir("", ""); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -135,3 +136,35 @@ func TestGetReadLength(t *testing.T) {
|
|||||||
t.Error("bad read length")
|
t.Error("bad read length")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetTmpFileWithText(t *testing.T) {
|
||||||
|
|
||||||
|
f := GetTmpFileWithText("some text")
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(f.Name()); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if _, err := os.Stat(f.Name()); os.IsNotExist(err) {
|
||||||
|
t.Error("tmp file does not exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTmpEmptyFile(t *testing.T) {
|
||||||
|
f := GetTmpEmptyFile()
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(f.Name()); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if _, err := os.Stat(f.Name()); os.IsNotExist(err) {
|
||||||
|
t.Error("tmp file does not exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTmpFileName(t *testing.T) {
|
||||||
|
f := GetTmpFileName()
|
||||||
|
if _, err := os.Stat(f); !os.IsNotExist(err) {
|
||||||
|
t.Error("tmp file exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package integration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEnvelop(t *testing.T) {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -5,19 +5,19 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/didiercrunch/filou/keygenerator"
|
"github.com/didiercrunch/filou/contract"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createKeyCommand = cli.Command{
|
var createKeyCommand = cli.Command{
|
||||||
Name: "createkey",
|
Name: "create-contracts",
|
||||||
Usage: "create a pair of public/private key",
|
Usage: "create a pair of public/private key encapsulated in contracts",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.IntFlag{Name: "size, s", Value: 2048, Usage: "key size in bits"},
|
cli.IntFlag{Name: "size, s", Value: 2048, Usage: "key size in bits"},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
keyGenerator := new(keygenerator.KeyGenerator)
|
keyGenerator := new(contract.ContractsGenerator)
|
||||||
fmt.Println("creating key")
|
fmt.Println("creating key ...")
|
||||||
if err := keyGenerator.CreateNewKey(c.Int("size")); err != nil {
|
if err := keyGenerator.CreateContracts(c.Int("size")); err != nil {
|
||||||
fmt.Println("error while creating the key\n", err)
|
fmt.Println("error while creating the key\n", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -28,14 +28,14 @@ var encryptFileCommand = cli.Command{
|
|||||||
Usage: "encrypt file or directory",
|
Usage: "encrypt file or directory",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{Name: "file, s", Value: "", Usage: "the file or directory to encrypt."},
|
cli.StringFlag{Name: "file, s", Value: "", Usage: "the file or directory to encrypt."},
|
||||||
cli.StringFlag{Name: "publicKey, k", Value: "", Usage: "the public key to use to encrypt"},
|
cli.StringFlag{Name: "publicContract, c", Value: "", Usage: "the public contract to use to encrypt"},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
keyGenerator := new(keygenerator.KeyGenerator)
|
if _, err := GetContract(c.String("publicContract")); err != nil {
|
||||||
fmt.Println("creating key")
|
fmt.Println("error getting the public contract", err)
|
||||||
if err := keyGenerator.CreateNewKey(c.Int("size")); err != nil {
|
return
|
||||||
fmt.Println("error while creating the key\n", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ func TestCreateKeyCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Commands = []cli.Command{c}
|
app.Commands = []cli.Command{c}
|
||||||
args := []string{"file", "createkey", "-size", "22"}
|
args := []string{"file", "create-contracts", "-size", "22"}
|
||||||
app.Run(args)
|
app.Run(args)
|
||||||
if !hasBeenCalled {
|
if !hasBeenCalled {
|
||||||
t.Error("sub command *createkey* has not been called")
|
t.Error("sub command *createkey* has not been called")
|
||||||
|
|||||||
Reference in new issue
Block a user