jx/internal/resource/container_test.go

273 lines
8.8 KiB
Go

// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package resource
import (
"context"
"decl/tests/mocks"
"decl/internal/codec"
_ "encoding/json"
_ "fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"io"
_ "net/http"
_ "net/http/httptest"
_ "net/url"
_ "os"
"strings"
"testing"
"bytes"
"time"
)
func TestNewContainerResource(t *testing.T) {
c := NewContainer(&mocks.MockContainerClient{})
assert.NotEqual(t, nil, c)
}
func TestReadContainer(t *testing.T) {
ctx := context.Background()
decl := `
name: "testcontainer"
image: "alpine"
state: present
`
m := &mocks.MockContainerClient{
InjectContainerList: func(ctx context.Context, options container.ListOptions) ([]types.Container, error) {
return []types.Container{
{ID: "123456789abc"},
{ID: "123456789def"},
}, nil
},
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: "123456789abc",
Name: "test",
Image: "alpine",
State: &types.ContainerState{
Status: "running",
},
}}, nil
},
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
var res container.WaitResponse
resChan := make(chan container.WaitResponse)
errChan := make(chan error, 1)
go func() { resChan <- res }()
return resChan, errChan
},
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("done.")), nil
},
}
c := NewContainer(m)
assert.NotNil(t, c)
e := c.LoadDecl(decl)
assert.Nil(t, e)
assert.Equal(t, "testcontainer", c.Name)
resourceYaml, readContainerErr := c.Read(ctx)
assert.Nil(t, readContainerErr)
assert.Greater(t, len(resourceYaml), 0)
assert.Equal(t, "running", c.State)
}
func TestCreateDeleteContainer(t *testing.T) {
m := &mocks.MockContainerClient{
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
},
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
return nil
},
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
return nil
},
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
var res container.WaitResponse
resChan := make(chan container.WaitResponse)
errChan := make(chan error, 1)
go func() { resChan <- res }()
return resChan, errChan
},
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("done.")), nil
},
}
decl := `
name: "testcontainer"
image: "alpine"
state: present
`
c := NewContainer(m)
e := c.LoadDecl(decl)
assert.Equal(t, nil, e)
assert.Equal(t, "testcontainer", c.Name)
applyErr := c.Apply()
assert.Equal(t, nil, applyErr)
c.State = "absent"
applyDeleteErr := c.Apply()
assert.Equal(t, nil, applyDeleteErr)
}
// Detect the ContainerLog header for each entry
func TestContainerLogOutput(t *testing.T) {
logHeader := []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x05}
logHeader = append(logHeader, []byte(string("done."))...)
logs := bytes.NewReader(logHeader)
m := &mocks.MockContainerClient{
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
},
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
return nil
},
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
return nil
},
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
var res container.WaitResponse
resChan := make(chan container.WaitResponse)
errChan := make(chan error, 1)
go func() { resChan <- res }()
return resChan, errChan
},
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
return io.NopCloser(logs), nil
},
}
c := NewContainer(m)
c.ReadFromContainer(context.Background())
assert.Equal(t, "done.", c.Stdout)
}
func TestWaitContainer(t *testing.T) {
mockState := &types.ContainerState{
Status: "",
}
m := &mocks.MockContainerClient{
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: "abcdef012",
Name: "testcontainer",
Image: "alpine",
State: mockState,
}}, nil
},
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
mockState.Status = "created"
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
},
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
return nil
},
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
return nil
},
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
var res container.WaitResponse
resChan := make(chan container.WaitResponse)
errChan := make(chan error, 1)
go func() { resChan <- res }()
return resChan, errChan
},
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("done.")), nil
},
}
c := NewContainer(m)
assert.Nil(t, c.LoadString(`
name: "testcontainer"
image: "alpine"
state: present
`, codec.FormatYaml))
assert.Nil(t, c.Apply())
assert.Equal(t, "testcontainer", c.Name)
assert.Nil(t, c.wait(context.Background(), func(state *types.ContainerState) bool {
return state.Status == "running"
}))
}
func TestRestartContainer(t *testing.T) {
mockState := &types.ContainerState{
Status: "",
}
m := &mocks.MockContainerClient{
InjectContainerInspect: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: "abcdef012",
Name: "testcontainer",
Image: "alpine",
State: mockState,
}}, nil
},
InjectContainerCreate: func(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
mockState.Status = "created"
return container.CreateResponse{ID: "abcdef012", Warnings: []string{}}, nil
},
InjectContainerStop: func(context.Context, string, container.StopOptions) error {
return nil
},
InjectContainerRemove: func(context.Context, string, container.RemoveOptions) error {
return nil
},
InjectContainerRestart: func(ctx context.Context, containerID string, options container.StopOptions) error {
go func() {
time.Sleep(100 * time.Millisecond)
mockState.Status = "running"
mockState.Running = true
}()
return nil
},
InjectContainerWait: func(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
var res container.WaitResponse
resChan := make(chan container.WaitResponse)
errChan := make(chan error, 1)
go func() { resChan <- res }()
return resChan, errChan
},
InjectContainerLogs: func(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("done.")), nil
},
}
c := NewContainer(m)
assert.Nil(t, c.LoadString(`
name: "testcontainer"
image: "alpine"
state: present
`, codec.FormatYaml))
assert.Equal(t, "testcontainer", c.Name)
assert.Nil(t, c.Apply())
c.State = "present" // overwrite the state
c.StateMachine().Trigger("restart")
assert.Equal(t, "running", c.State)
assert.Nil(t, c.Apply())
}