Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Visit the [Release page](https://github.com/microcks/microcks-cli/releases/tag/1
OR you can use the [Homebrew](https://brew.sh/) package manager on Linux and MacOS that way:

```sh
brew tap microcks/tap
brew tap microcks/tap
brew install microcks/tap/microcks
```

Expand Down Expand Up @@ -89,7 +89,39 @@ microcks [command] [flags]
| `--microcksURL` | Microcks API URL |


### Local contract testing without a server

`microcks test --dry-run` runs a contract test with zero infrastructure: no running Microcks server, no Keycloak credentials, no upfront import. The CLI spins up an ephemeral Microcks container (via [Testcontainers](https://microcks.io/documentation/guides/usage/developing-testcontainers/)), imports your spec, runs the test against your endpoint, prints the result and tears the container down.

```bash
# One-shot: run once, tear down, exit (exit code 0/1 reflects the test result)
microcks test --dry-run \
--artifact ./openapi.yaml \
"Pastry API:1.0.0" \
http://localhost:3000 \
OPEN_API_SCHEMA

# Watch mode: keep the container alive, re-import + re-run on every save (TDD loop)
microcks test --dry-run --watch \
--artifact ./openapi.yaml \
"Pastry API:1.0.0" \
http://localhost:3000 \
OPEN_API_SCHEMA
```

| Flag | Default | Description |
| ---- | ------- | ----------- |
| `--dry-run` | `false` | Activate the ephemeral-container path |
| `--artifact` | _(required with `--dry-run`)_ | Local spec file imported as main artifact |
| `--image` | `quay.io/microcks/microcks-uber:latest-native` | Uber image override (must be a `*-native` tag) |
| `--ready-timeout` | `90s` | How long to wait for the container to be ready |
| `--watch` | `false` | Re-run the test when the artifact file changes |

Notes:

- A `localhost`/`127.0.0.1` test endpoint is automatically reachable from inside the container — the CLI exposes the port and rewrites the endpoint for you.
- The container is removed on every exit path, including `Ctrl+C` mid-test.
- Docker is the primary runtime; Podman works through its Docker-compatible socket (`DOCKER_HOST`).

### Building from Source

Expand Down
80 changes: 48 additions & 32 deletions cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
filteredOperations string
operationsHeaders string
oAuth2Context string
dryRun bool
artifact string
image string
readyTimeout time.Duration
watch bool
)
var testCmd = &cobra.Command{

Expand Down Expand Up @@ -106,6 +111,42 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
waitForMilliseconds = n * 60 * 1000
}

params := testParams{
serviceRef: serviceRef,
testEndpoint: testEndpoint,
runnerType: runnerType,
secretName: secretName,
waitForMillis: waitForMilliseconds,
filteredOperations: filteredOperations,
operationsHeaders: operationsHeaders,
oAuth2Context: oAuth2Context,
}

if !dryRun {
if artifact != "" {
fmt.Println("--artifact is only valid together with --dry-run")
os.Exit(1)
}
if watch {
fmt.Println("--watch is only valid together with --dry-run")
os.Exit(1)
}
}

if dryRun {
// Ephemeral path: no server, no Keycloak, no prior import needed.
if !runDryRunTest(dryRunOptions{
artifact: artifact,
image: image,
readyTimeout: readyTimeout,
watch: watch,
params: params,
}) {
os.Exit(1)
}
return
}

var mc connectors.MicrocksClient
var serverAddr string

Expand Down Expand Up @@ -165,38 +206,11 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
serverAddr = ctx.Server.Server
}

testResultID, err := mc.CreateTestResult(serviceRef, testEndpoint, runnerType, secretName, waitForMilliseconds, filteredOperations, operationsHeaders, oAuth2Context)
success, testResultID, err := runTestAndWait(mc, params)
if err != nil {
fmt.Printf("Got error when invoking Microcks client creating Test: %s", err)
fmt.Print(err)
os.Exit(1)
}
//fmt.Printf("Retrieve TestResult ID: %s", testResultID)

// Finally - wait before checking and loop for some time
time.Sleep(1 * time.Second)

// Add 10.000ms to wait time as it's now representing the server timeout.
now := nowInMilliseconds()
future := now + waitForMilliseconds + 10000

var success = false
for nowInMilliseconds() < future {
testResultSummary, err := mc.GetTestResult(testResultID)
if err != nil {
fmt.Printf("Got error when invoking Microcks client check TestResult: %s", err)
os.Exit(1)
}
success = testResultSummary.Success
inProgress := testResultSummary.InProgress
fmt.Printf("MicrocksClient got status for test \"%s\" - success: %s, inProgress: %s \n", testResultID, fmt.Sprint(success), fmt.Sprint(inProgress))

if !inProgress {
break
}

fmt.Println("MicrocksTester waiting for 2 seconds before checking again or exiting.")
time.Sleep(2 * time.Second)
}

fmt.Printf("Full TestResult details are available here: %s/#/tests/%s \n", serverAddr, testResultID)

Expand All @@ -211,10 +225,12 @@ func NewTestCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
testCmd.Flags().StringVar(&filteredOperations, "filteredOperations", "", "List of operations to launch a test for")
testCmd.Flags().StringVar(&operationsHeaders, "operationsHeaders", "", "Override of operations headers as JSON string")
testCmd.Flags().StringVar(&oAuth2Context, "oAuth2Context", "", "Spec of an OAuth2 client context as JSON string")
testCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Run the test against an ephemeral local Microcks container instead of a server")
testCmd.Flags().StringVar(&artifact, "artifact", "", "Local spec file to import on the ephemeral server (required with --dry-run)")
testCmd.Flags().StringVar(&image, "image", defaultDryRunImage, "Microcks uber-native image used for --dry-run")
testCmd.Flags().DurationVar(&readyTimeout, "ready-timeout", 90*time.Second, "How long to wait for the ephemeral container to be ready (--dry-run only)")
testCmd.Flags().BoolVar(&watch, "watch", false, "Watch the artifact file and re-run the test on change (--dry-run only)")

return testCmd
}

func nowInMilliseconds() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}
Loading
Loading