-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from JulienBreux/feat/add-postman
Allow Postman contract-testing support using ensemble design
- Loading branch information
Showing
9 changed files
with
750 additions
and
225 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
package ensemble | ||
|
||
import ( | ||
"context" | ||
"strings" | ||
|
||
"github.com/testcontainers/testcontainers-go" | ||
"github.com/testcontainers/testcontainers-go/network" | ||
microcks "microcks.io/testcontainers-go" | ||
"microcks.io/testcontainers-go/ensemble/postman" | ||
) | ||
|
||
// Option represents an option to pass to the ensemble | ||
type Option func(*MicrocksContainersEnsemble) error | ||
|
||
// ContainerOptions represents the container options | ||
type ContainerOptions struct { | ||
list []testcontainers.ContainerCustomizer | ||
} | ||
|
||
// Add adds an option to the list | ||
func (co *ContainerOptions) Add(opt testcontainers.ContainerCustomizer) { | ||
co.list = append(co.list, opt) | ||
} | ||
|
||
// MicrocksContainersEnsemble represents the ensemble of containers | ||
type MicrocksContainersEnsemble struct { | ||
ctx context.Context | ||
|
||
network *testcontainers.DockerNetwork | ||
|
||
microcksContainer *microcks.MicrocksContainer | ||
microcksContainerOptions ContainerOptions | ||
|
||
postmanEnabled bool | ||
postmanContainer *postman.PostmanContainer | ||
postmanContainerOptions ContainerOptions | ||
} | ||
|
||
// GetNetwork returns the ensemble network | ||
func (ec *MicrocksContainersEnsemble) GetNetwork() *testcontainers.DockerNetwork { | ||
return ec.network | ||
} | ||
|
||
// GetMicrocksContainer returns the Microcks container | ||
func (ec *MicrocksContainersEnsemble) GetMicrocksContainer() *microcks.MicrocksContainer { | ||
return ec.microcksContainer | ||
} | ||
|
||
// GetPostmanContainer returns the Postman container | ||
func (ec *MicrocksContainersEnsemble) GetPostmanContainer() *postman.PostmanContainer { | ||
return ec.postmanContainer | ||
} | ||
|
||
// Terminate helps to terminate all containers | ||
func (ec *MicrocksContainersEnsemble) Terminate(ctx context.Context) error { | ||
// Main Microcks container | ||
if err := ec.microcksContainer.Terminate(ctx); err != nil { | ||
return err | ||
} | ||
|
||
// Postman container | ||
if ec.postmanEnabled { | ||
if err := ec.postmanContainer.Terminate(ctx); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// RunContainers creates instances of the Microcks and necessaries tools. | ||
// Using sequential start to avoid resource contention on CI systems with weaker hardware. | ||
func RunContainers(ctx context.Context, opts ...Option) (*MicrocksContainersEnsemble, error) { | ||
var err error | ||
|
||
ensemble := &MicrocksContainersEnsemble{ctx: ctx} | ||
|
||
// Options | ||
defaults := []Option{WithDefaultNetwork()} | ||
options := append(defaults, opts...) | ||
for _, opt := range options { | ||
if err = opt(ensemble); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
// Microcks container | ||
if ensemble.postmanEnabled { | ||
postmanRunnerURL := strings.Join([]string{"http://", postman.DefaultNetworkAlias, ":3000"}, "") | ||
ensemble.microcksContainerOptions.Add(microcks.WithEnv("POSTMAN_RUNNER_URL", postmanRunnerURL)) | ||
} | ||
testCallbackURL := strings.Join([]string{"http://", microcks.DefaultNetworkAlias, ":8080"}, "") | ||
ensemble.microcksContainerOptions.Add(microcks.WithEnv("TEST_CALLBACK_URL", testCallbackURL)) | ||
ensemble.microcksContainer, err = microcks.RunContainer(ctx, ensemble.microcksContainerOptions.list...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Postman container | ||
if ensemble.postmanEnabled { | ||
ensemble.postmanContainer, err = postman.RunContainer(ctx, ensemble.postmanContainerOptions.list...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return ensemble, nil | ||
} | ||
|
||
// WithDefaultNetwork allows to use a default network | ||
func WithDefaultNetwork() Option { | ||
return func(e *MicrocksContainersEnsemble) (err error) { | ||
e.network, err = network.New(e.ctx, network.WithCheckDuplicate()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
e.microcksContainerOptions.Add(microcks.WithNetwork(e.network.Name)) | ||
e.microcksContainerOptions.Add(microcks.WithNetworkAlias(e.network.Name, microcks.DefaultNetworkAlias)) | ||
e.postmanContainerOptions.Add(postman.WithNetwork(e.network.Name)) | ||
e.postmanContainerOptions.Add(postman.WithNetworkAlias(e.network.Name, postman.DefaultNetworkAlias)) | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// WithNetwork allows to define the network | ||
func WithNetwork(network *testcontainers.DockerNetwork) Option { | ||
return func(e *MicrocksContainersEnsemble) error { | ||
e.network = network | ||
e.microcksContainerOptions.Add(microcks.WithNetwork(e.network.Name)) | ||
e.microcksContainerOptions.Add(microcks.WithNetworkAlias(e.network.Name, microcks.DefaultNetworkAlias)) | ||
e.postmanContainerOptions.Add(postman.WithNetwork(e.network.Name)) | ||
e.postmanContainerOptions.Add(postman.WithNetworkAlias(e.network.Name, postman.DefaultNetworkAlias)) | ||
return nil | ||
} | ||
} | ||
|
||
// WithMainArtifact provides paths to artifacts that will be imported as main or main | ||
// ones within the Microcks container. | ||
// Once it will be started and healthy. | ||
func WithMainArtifact(artifactFilePath string) Option { | ||
return func(e *MicrocksContainersEnsemble) error { | ||
e.microcksContainerOptions.Add(microcks.WithMainArtifact(artifactFilePath)) | ||
return nil | ||
} | ||
} | ||
|
||
// WithSecondaryArtifact provides paths to artifacts that will be imported as main or main | ||
// ones within the Microcks container. | ||
// Once it will be started and healthy. | ||
func WithSecondaryArtifact(artifactFilePath string) Option { | ||
return func(e *MicrocksContainersEnsemble) error { | ||
e.microcksContainerOptions.Add(microcks.WithSecondaryArtifact(artifactFilePath)) | ||
return nil | ||
} | ||
} | ||
|
||
// WithPostman allows to enable Postman container | ||
func WithPostman(enable bool) Option { | ||
return func(e *MicrocksContainersEnsemble) error { | ||
e.postmanEnabled = enable | ||
return nil | ||
} | ||
} | ||
|
||
// WithPostmanImage helps to use specific Postman image | ||
func WithPostmanImage(image string) Option { | ||
return func(e *MicrocksContainersEnsemble) error { | ||
e.postmanContainerOptions.Add(testcontainers.WithImage(image)) | ||
e.postmanEnabled = true | ||
return nil | ||
} | ||
} | ||
|
||
// WithMicrocksImage helps to use specific Microcks image | ||
func WithMicrocksImage(image string) Option { | ||
return func(e *MicrocksContainersEnsemble) error { | ||
e.postmanContainerOptions.Add(testcontainers.WithImage(image)) | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package ensemble_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/testcontainers/testcontainers-go" | ||
"github.com/testcontainers/testcontainers-go/wait" | ||
"microcks.io/testcontainers-go/ensemble" | ||
"microcks.io/testcontainers-go/internal/test" | ||
) | ||
|
||
func TestMockingFunctionalityAtStartup(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
ec, err := ensemble.RunContainers(ctx, | ||
ensemble.WithMainArtifact("../testdata/apipastries-openapi.yaml"), | ||
ensemble.WithSecondaryArtifact("../testdata/apipastries-postman-collection.json"), | ||
) | ||
require.NoError(t, err) | ||
t.Cleanup(func() { | ||
if err := ec.Terminate(ctx); err != nil { | ||
t.Fatalf("failed to terminate container: %s", err) | ||
} | ||
}) | ||
|
||
test.ConfigRetrieval(t, ctx, ec.GetMicrocksContainer()) | ||
test.MockEndpoints(t, ctx, ec.GetMicrocksContainer()) | ||
|
||
test.MicrocksMockingFunctionality(t, ctx, ec.GetMicrocksContainer()) | ||
} | ||
|
||
func TestPostmanContractTestingFunctionality(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
// Ensemble | ||
ec, err := ensemble.RunContainers( | ||
ctx, | ||
ensemble.WithMainArtifact("../testdata/apipastries-openapi.yaml"), | ||
ensemble.WithSecondaryArtifact("../testdata/apipastries-postman-collection.json"), | ||
ensemble.WithPostman(true), | ||
) | ||
require.NoError(t, err) | ||
networkName := ec.GetNetwork().Name | ||
|
||
// Demo pastry bad implementation | ||
badImpl, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ | ||
ContainerRequest: testcontainers.ContainerRequest{ | ||
Image: "quay.io/microcks/contract-testing-demo:02", | ||
Networks: []string{networkName}, | ||
NetworkAliases: map[string][]string{ | ||
networkName: {"bad-impl"}, | ||
}, | ||
WaitingFor: wait.ForLog("Example app listening on port 3002"), | ||
}, | ||
Started: true, | ||
}) | ||
require.NoError(t, err) | ||
|
||
// Demo pastry good implementation | ||
goodImpl, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ | ||
ContainerRequest: testcontainers.ContainerRequest{ | ||
Image: "quay.io/microcks/contract-testing-demo:03", | ||
Networks: []string{networkName}, | ||
NetworkAliases: map[string][]string{ | ||
networkName: {"good-impl"}, | ||
}, | ||
WaitingFor: wait.ForLog("Example app listening on port 3003"), | ||
}, | ||
Started: true, | ||
}) | ||
require.NoError(t, err) | ||
|
||
// Cleanup containers | ||
t.Cleanup(func() { | ||
if err := ec.GetMicrocksContainer().Terminate(ctx); err != nil { | ||
t.Fatalf("failed to terminate container: %s", err) | ||
} | ||
if err := badImpl.Terminate(ctx); err != nil { | ||
t.Fatalf("failed to terminate container: %s", err) | ||
} | ||
if err := goodImpl.Terminate(ctx); err != nil { | ||
t.Fatalf("failed to terminate container: %s", err) | ||
} | ||
}) | ||
|
||
// Tests & assertions | ||
test.ConfigRetrieval(t, ctx, ec.GetMicrocksContainer()) | ||
test.MicrocksContractTestingFunctionality( | ||
t, | ||
ctx, | ||
ec.GetMicrocksContainer(), | ||
badImpl, | ||
goodImpl, | ||
) | ||
} |
Oops, something went wrong.