Skip to content

Commit

Permalink
feat: add cli test mode and profile tests (#313)
Browse files Browse the repository at this point in the history
CLI supports building in test mode to enable mocking the profile
See README for details.

---------

Co-authored-by: jakedoublev <jake.vanvorhis@virtru.com>
  • Loading branch information
jrschumacher and jakedoublev authored Aug 29, 2024
1 parent ed21f81 commit e0bc183
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ jobs:
end-to-end:
name: e2e tests
runs-on: ubuntu-22.04
env:
BATS_LIB_PATH: /usr/lib
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Check out platform
Expand Down Expand Up @@ -98,10 +100,13 @@ jobs:

- name: build the CLI
run: go build .
- name: build the CLI in test mode
run: make build-test
- name: set up the config
run: cp otdfctl-example.yaml otdfctl.yaml
- name: Setup Bats and bats libs
uses: bats-core/bats-action@2.0.0
- run: tests/encrypt-decrypt.bats
- run: tests/kas-grants.bats
- run: tests/profile.bats
- run: tests/kas-registry.bats
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ target/
.vscode/launch.json
otdfctl.yaml

# Ignore the tructl binary
# Ignore the binaries
otdfctl
otdfctl.*
otdfctl_testbuild
otdfctl_testbuild.*

# Misc
# Test artifacts
creds.json

# Hugo
Expand Down
38 changes: 34 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ GO_MOD_LINE = $(shell head -n 1 go.mod | cut -c 8-)
GO_MOD_NAME = $(word 1,$(subst /, ,$(GO_MOD_LINE)))
APP_CFG = $(GO_MOD_LINE)/pkg/config

GO_BUILD_FLAGS=-ldflags "-X $(APP_CFG).Version=${CURR_VERSION} -X $(APP_CFG).CommitSha=${COMMIT_SHA} -X $(APP_CFG).BuildTime=${BUILD_TIME}"
GO_BUILD_FLAGS=-ldflags " \
-X $(APP_CFG).Version=${CURR_VERSION} \
-X $(APP_CFG).CommitSha=${COMMIT_SHA} \
-X $(APP_CFG).BuildTime=${BUILD_TIME} \
"
GO_BUILD_PREFIX=$(TARGET_DIR)/$(BINARY_NAME)-${CURR_VERSION}

# If commit sha is not available try git
Expand All @@ -35,13 +39,24 @@ TARGET_DIR=target
# Output directory for the zipped artifacts
OUTPUT_DIR=output

# Build commands for each platform
PLATFORMS := darwin-amd64 darwin-arm64 linux-amd64 linux-arm linux-arm64 windows-amd64-.exe windows-arm-.exe windows-arm64-.exe
# Build commands for each platform (extra hyphen used in windows to avoid issues with the .exe extension)
PLATFORMS := \
darwin-amd64 \
darwin-arm64 \
linux-amd64 \
linux-arm \
linux-arm64 \
windows-amd64-.exe \
windows-arm-.exe \
windows-arm64-.exe

build: test clean $(addprefix build-,$(PLATFORMS)) zip-builds verify-checksums

build-%:
GOOS=$(word 1,$(subst -, ,$*)) GOARCH=$(word 2,$(subst -, ,$*)) go build $(GO_BUILD_FLAGS) -o $(GO_BUILD_PREFIX)-$(word 1,$(subst -, ,$*))-$(word 2,$(subst -, ,$*))$(word 3,$(subst -, ,$*))
GOOS=$(word 1,$(subst -, ,$*)) \
GOARCH=$(word 2,$(subst -, ,$*)) \
go build $(GO_BUILD_FLAGS) \
-o $(GO_BUILD_PREFIX)-$(word 1,$(subst -, ,$*))-$(word 2,$(subst -, ,$*))$(word 3,$(subst -, ,$*))

zip-builds:
./.github/scripts/zip-builds.sh $(BINARY_NAME)-$(CURR_VERSION) $(TARGET_DIR) $(OUTPUT_DIR)
Expand All @@ -59,6 +74,21 @@ run:
test:
go test -v ./...

.PHONY: build-test
build-test:
go build \
-ldflags "\
-X $(APP_CFG).TestMode=true \
-X $(APP_CFG).Version=${CURR_VERSION}-testbuild \
-X $(APP_CFG).CommitSha=${COMMIT_SHA} \
-X $(APP_CFG).BuildTime=${BUILD_TIME} \
" \
-o $(BINARY_NAME)_testbuild

.PHONY: test-bats
test-bats: build-test
bats ./tests

# Target for cleaning up the target directory
.PHONY: clean
clean:
Expand Down
49 changes: 22 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,30 +54,25 @@ CLI via the `man.Docs.GetDoc()` function.

## Testing

The [tests](./tests) directory contains e2e Bash Automated Test System (bats) tests for all of the cli functionality.

To install bats on MacOS:
```
$ brew install bats-core
```
Or with NPM on any OS:
```
# To install globally:
$ npm install -g bats
# To install into your project and save it as one of the "devDependencies" in
# your package.json:
$ npm install --save-dev bats
```

These tests require the platform to be running and provisioned with basic keycloak clients/users. Before running, clone https://github.com/opentdf/platform and follow [the quickstart](https://github.com/opentdf/platform?tab=readme-ov-file#quick-start) to spin it up.

Build the cli:
```
$ go build .
```

Run the bats with:
```
$ bats tests/*.bats
```
The CLI is equipped with a test mode that can be enabled by building the CLI with `config.TestMode = true`.
For convenience, the CLI can be built with `make build-test`.

**Test Mode features**:

- Use the in-memory keyring provider for user profiles
- Enable provisioning profiles for testing via `OTDFCTL_TEST_PROFILE` environment variable

### BATS

> [!NOTE]
> Bat Automated Test System (bats) is a TAP-compliant testing framework for Bash. It provides a simple way to verify that the UNIX programs you write behave as expected.
BATS is used to test the CLI from an end-to-end perspective. To run the tests you will need to ensure the following
prerequisites are met:

- bats is installed on your system
- MacOS: `brew install bats-core bats-support bats-assert`
- The platform is running and provisioned with basic keycloak clients/users
- See the [platform README](https://github.com/opentdf/platform) for instructions

To run the tests you can either run `make test-bats` or execute specific test suites with `bats tests/<test>.bats`.
5 changes: 3 additions & 2 deletions cmd/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"runtime"

"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/config"
"github.com/opentdf/otdfctl/pkg/profiles"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -167,8 +168,8 @@ var profileSetEndpointCmd = &cobra.Command{
}

func init() {
// Profiles are not supported on Linux
if runtime.GOOS == "linux" {
// Profiles are not supported on Linux (unless mocked in test mode)
if runtime.GOOS == "linux" && config.TestMode != "true" {
return
}

Expand Down
22 changes: 14 additions & 8 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ import (
"github.com/spf13/viper"
)

// AppName is the name of the application
// Note: use caution when renaming as it is used in various places within the CLI including for config file naming
// and in the profile store
var AppName = "otdfctl"

var Version = "0.0.0"
var BuildTime = "1970-01-01T00:00:00Z"
var CommitSha = "0000000"
var (
// AppName is the name of the application
// Note: use caution when renaming as it is used in various places within the CLI including for
// config file naming and in the profile store
AppName = "otdfctl"

Version = "0.0.0"
BuildTime = "1970-01-01T00:00:00Z"
CommitSha = "0000000"

// Test mode is used to determine if the application is running in test mode
// "true" = running in test mode
TestMode = ""
)

type Output struct {
Format string `yaml:"format" default:"styled"`
Expand Down
4 changes: 4 additions & 0 deletions pkg/profiles/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ func newStoreFactory(driver string) NewStoreInterface {
func New(opts ...profileConfigVariadicFunc) (*Profile, error) {
var err error

if testProfile != nil {
return testProfile, nil
}

config := profileConfig{
driver: PROFILE_DRIVER_DEFAULT,
}
Expand Down
2 changes: 0 additions & 2 deletions pkg/profiles/storeKeyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"github.com/zalando/go-keyring"
)

// TODO: update the store to use alternative storage methods besides keyring

type KeyringStore struct {
namespace string
key string
Expand Down
74 changes: 74 additions & 0 deletions pkg/profiles/test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package profiles

import (
"bytes"
"encoding/json"
"fmt"
"os"

"github.com/opentdf/otdfctl/pkg/config"
"github.com/zalando/go-keyring"
)

const testModeMsg = `
********************
RUNNING IN TEST MODE
test config: %s
********************
`

var (
testProfile *Profile
testCfg = os.Getenv("OTDFCTL_TEST_PROFILE")
)

type testConfig struct {
// global config is used to get the store in a bad state
GlobalConfig config.Config `json:"globalConfig,omitempty"`

// set the default profile
DefaultProfile string `json:"defaultProfile,omitempty"`

// profiles to add
Profiles []ProfileConfig `json:"profiles,omitempty"`
}

func init() {
// If running in test mode, use the mock keyring
if config.TestMode == "true" {
fmt.Printf(testModeMsg, testCfg)

keyring.MockInit()

// configure the keyring based on the test config
// unmarsal the test config
if testCfg != "" {
var err error
var cfg testConfig
if err := json.NewDecoder(bytes.NewReader([]byte(testCfg))).Decode(&cfg); err != nil {
panic(err)
}

testProfile, err = New()
if err != nil {
panic(err)
}

for _, p := range cfg.Profiles {
err := testProfile.AddProfile(p.Name, p.Endpoint, p.TlsNoVerify, cfg.DefaultProfile == p.Name)
if err != nil {
panic(err)
}
}

// set default
if cfg.DefaultProfile != "" {
if err := testProfile.SetDefaultProfile(cfg.DefaultProfile); err != nil {
panic(err)
}
}
}
}
}
Loading

0 comments on commit e0bc183

Please sign in to comment.