mirror of https://github.com/knative/func.git
300 lines
9.2 KiB
Go
300 lines
9.2 KiB
Go
package client_test
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/lkingland/faas/client"
|
|
"github.com/lkingland/faas/client/mock"
|
|
)
|
|
|
|
// TestNew ensures that instantiation succeeds or fails as expected.
|
|
func TestNew(t *testing.T) {
|
|
// Instantiation with optional explicit service name should succeed.
|
|
_, err := client.New(
|
|
client.WithName("my.example.com")) // be explicit rather than path-derive
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Instantiation with optional verbosity should succeed.
|
|
_, err = client.New(
|
|
client.WithName("my.example.com"),
|
|
client.WithVerbose(true),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Instantiation without an explicit service name, but no derivable service
|
|
// name (because of limiting path recursion) should fail.
|
|
_, err = client.New(
|
|
client.WithDomainSearchLimit(0), // limit ability to derive from path.
|
|
)
|
|
if err == nil {
|
|
t.Fatal("no error generated for unspecified and underivable name")
|
|
}
|
|
}
|
|
|
|
// TestCreate ensures that instantiation completes without error when provided with a
|
|
// valid language. A single client instance services a single Service Function instance
|
|
// and as such requires the desired effective DNS for the function. This is an optional
|
|
// parameter, as it is derived from path by default.
|
|
func TestCreate(t *testing.T) {
|
|
client, err := client.New(
|
|
client.WithName("my.example.com"),
|
|
client.WithInitializer(mock.NewInitializer()),
|
|
) // be explicit rather than path-derive
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a Function Service call missing language should error
|
|
if err := client.Create(""); err == nil {
|
|
t.Fatal("missing language did not generate error")
|
|
}
|
|
|
|
// create a Function Service call witn an unsupported language should bubble
|
|
// the error generated by the underlying initializer.
|
|
if err := client.Create("cobol"); err == nil {
|
|
t.Fatal("unsupported language did not generate error")
|
|
}
|
|
|
|
// A supported langauge should not error.
|
|
if err := client.Create("go"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestCreateDelegeates ensures that a call to Create invokes the Service Function
|
|
// Initializer, Builder, Pusher and Deployer with expected parameters.
|
|
func TestCreateDelegates(t *testing.T) {
|
|
var (
|
|
path = "testdata/example.com/admin" // .. in which to initialize
|
|
name = "admin.example.com" // expected to be derived
|
|
image = "my.hub/user/imagestamp" // expected image
|
|
route = "https://admin.example.com/" // expected final route
|
|
initializer = mock.NewInitializer()
|
|
builder = mock.NewBuilder()
|
|
pusher = mock.NewPusher()
|
|
deployer = mock.NewDeployer()
|
|
)
|
|
|
|
client, err := client.New(
|
|
client.WithRoot(path), // set function root
|
|
client.WithInitializer(initializer), // will receive the final value
|
|
client.WithBuilder(builder), // builds an image
|
|
client.WithPusher(pusher), // pushes images to a registry
|
|
client.WithDeployer(deployer), // deploys images as a running service
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Register function delegates on the mocks which validate assertions
|
|
// -------------
|
|
|
|
// The initializer should receive the name expected from the path,
|
|
// the passed language, and an absolute path to the funciton soruce.
|
|
initializer.InitializeFn = func(name, language, path string) error {
|
|
if name != "admin.example.com" {
|
|
t.Fatalf("initializer expected name 'admin.example.com', got '%v'", name)
|
|
}
|
|
if language != "go" {
|
|
t.Fatalf("initializer expected language 'go', got '%v'", language)
|
|
}
|
|
expectedPath, err := filepath.Abs("./testdata/example.com/admin")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if path != expectedPath {
|
|
t.Fatalf("initializer expected path '%v', got '%v'", expectedPath, path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// The builder should be invoked with a service name and path to its source
|
|
// function code. For this test, it is a name derived from the test path.
|
|
// An example image name is returned.
|
|
builder.BuildFn = func(name2, path2 string) (string, error) {
|
|
if name != name {
|
|
t.Fatalf("builder expected name %v, got '%v'", name, name2)
|
|
}
|
|
expectedPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if path2 != expectedPath {
|
|
t.Fatalf("builder expected path '%v', got '%v'", expectedPath, path)
|
|
}
|
|
// The final image name will be determined by the builder implementation,
|
|
// but whatever it is (in this case fabricarted); it should be returned
|
|
// and later provided to the pusher.
|
|
return image, nil
|
|
}
|
|
|
|
// The pusher should be invoked with the image to push.
|
|
pusher.PushFn = func(image2 string) error {
|
|
if image2 != image {
|
|
t.Fatalf("pusher expected image '%v', got '%v'", image, image2)
|
|
}
|
|
// image of given name wouold be pushed to the configured registry.
|
|
return nil
|
|
}
|
|
|
|
// The deployer should be invoked with the service name and image, and return
|
|
// the final accessible address.
|
|
deployer.DeployFn = func(name2 string) (address string, err error) {
|
|
if name2 != name {
|
|
t.Fatalf("deployer expected name '%v', got '%v'", name, name2)
|
|
}
|
|
// service of given name would be deployed using the given image and
|
|
// allocated route returned.
|
|
return route, nil
|
|
}
|
|
|
|
// Invocation
|
|
// -------------
|
|
|
|
// Invoke the creation, triggering the function delegates, and
|
|
// perform follow-up assertions that the functions were indeed invoked.
|
|
if err := client.Create("go"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Confirm that each delegate was invoked.
|
|
if !initializer.InitializeInvoked {
|
|
t.Fatal("initializer was not invoked")
|
|
}
|
|
if !builder.BuildInvoked {
|
|
t.Fatal("builder was not invoked")
|
|
}
|
|
if !pusher.PushInvoked {
|
|
t.Fatal("pusher was not invoked")
|
|
}
|
|
if !deployer.DeployInvoked {
|
|
t.Fatal("deployer was not invoked")
|
|
}
|
|
}
|
|
|
|
// TestCreateLocal ensures that when set to local-only mode, Create only invokes
|
|
// the initializer and builder.
|
|
func TestCreateLocal(t *testing.T) {
|
|
var (
|
|
path = "testdata/example.com/admin"
|
|
initializer = mock.NewInitializer()
|
|
builder = mock.NewBuilder()
|
|
pusher = mock.NewPusher()
|
|
deployer = mock.NewDeployer()
|
|
dnsProvider = mock.NewDNSProvider()
|
|
)
|
|
|
|
client, err := client.New(
|
|
client.WithRoot(path), // set function root
|
|
client.WithInitializer(initializer), // will receive the final value
|
|
client.WithBuilder(builder), // builds an image
|
|
client.WithPusher(pusher), // pushes images to a registry
|
|
client.WithDeployer(deployer), // deploys images as a running service
|
|
client.WithDNSProvider(dnsProvider), // will receive the final value
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Set the client to local-only mode
|
|
client.SetLocal(true)
|
|
|
|
// Create a new Service Function
|
|
if err := client.Create("go"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Ensure that none of the remote delegates were invoked
|
|
if pusher.PushInvoked {
|
|
t.Fatal("Push invoked in local mode.")
|
|
}
|
|
if deployer.DeployInvoked {
|
|
t.Fatal("Deploy invoked in local mode.")
|
|
}
|
|
if dnsProvider.ProvideInvoked {
|
|
t.Fatal("DNS provider invoked in local mode.")
|
|
}
|
|
|
|
}
|
|
|
|
// TestCreateDomain ensures that the effective domain is dervied from
|
|
// directory structure. See the unit tests for pathToDomain for details.
|
|
func TestCreateDomain(t *testing.T) {
|
|
// the mock dns provider does nothing but receive the caluclated
|
|
// domain name via it's Provide(domain) method, which is the value
|
|
// being tested here.
|
|
dnsProvider := mock.NewDNSProvider()
|
|
|
|
client, err := client.New(
|
|
client.WithRoot("./testdata/example.com"), // set function root
|
|
client.WithDomainSearchLimit(1), // Limit recursion to one level
|
|
client.WithDNSProvider(dnsProvider), // will receive the final value
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := client.Create("go"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !dnsProvider.ProvideInvoked {
|
|
t.Fatal("dns provider was not invoked")
|
|
}
|
|
if dnsProvider.NameRequested != "example.com" {
|
|
t.Fatalf("expected 'example.com', got '%v'", dnsProvider.NameRequested)
|
|
}
|
|
}
|
|
|
|
// TestCreateSubdomain ensures that a subdirectory is interpreted as a subdomain
|
|
// when calculating final domain. See the unit tests for pathToDomain for the
|
|
// details and edge cases of this caluclation.
|
|
func TestCreateSubdomain(t *testing.T) {
|
|
dnsProvider := mock.NewDNSProvider()
|
|
client, err := client.New(
|
|
client.WithRoot("./testdata/example.com/admin"),
|
|
client.WithDomainSearchLimit(2),
|
|
client.WithDNSProvider(dnsProvider),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Create("go"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !dnsProvider.ProvideInvoked {
|
|
t.Fatal("dns provider was not invoked")
|
|
}
|
|
if dnsProvider.NameRequested != "admin.example.com" {
|
|
t.Fatalf("expected 'admin.example.com', got '%v'", dnsProvider.NameRequested)
|
|
}
|
|
}
|
|
|
|
// TestRun ensures that the runner is invoked with the absolute path requested.
|
|
func TestRun(t *testing.T) {
|
|
root := "./testdata/example.com/admin"
|
|
runner := mock.NewRunner()
|
|
client, err := client.New(
|
|
client.WithRoot(root),
|
|
client.WithRunner(runner),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !runner.RunInvoked {
|
|
t.Fatal("run did not invoke the runner")
|
|
}
|
|
absRoot, err := filepath.Abs(root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if runner.RootRequested != absRoot {
|
|
t.Fatalf("expected path '%v', got '%v'", absRoot, runner.RootRequested)
|
|
}
|
|
}
|