func TestDaemon_Release(t *testing.T) {
	d, start, clean, _, _, _ := mockDaemon(t)
	start()
	defer clean()
	w := newWait(t)
	ctx := context.Background()
	id := updateImage(ctx, d, t)
	stat, err := d.JobStatus(ctx, id)
	if err != nil {
		t.Fatalf("Error: %s", err.Error())
	} else if stat.Err != "" {
		t.Fatal("Job status error should be empty")
	} else if stat.StatusString != job.StatusQueued {
		t.Fatalf("Expected %v but got %v", job.StatusQueued, stat.StatusString)
	}
	w.ForJobSucceeded(d, id)
	w.Eventually(func() bool {
		co, err := d.Repo.Clone(ctx, d.GitConfig)
		if err != nil {
			return false
		}
		defer co.Clean()
		dirs := co.AbsolutePaths()
		if file, err := os.Open(filepath.Join(dirs[0], "helloworld-deploy.yaml")); err == nil {
			defer file.Close()
			scanner := bufio.NewScanner(file)
			for scanner.Scan() {
				if strings.Contains(scanner.Text(), newHelloImage) {
					return true
				}
			}
		} else {
			t.Fatal(err)
		}
		return false
	}, "Waiting for new manifest")
}
When I perform a release, it should add a job to update git to the queue
When I ask about a Job, it should tell me about a job
When I perform a release, it should update the git repo
package daemon
import (
	"bufio"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"
	"github.com/go-kit/kit/log"
	"github.com/stretchr/testify/assert"
	"github.com/fluxcd/flux/pkg/api/v10"
	"github.com/fluxcd/flux/pkg/api/v11"
	"github.com/fluxcd/flux/pkg/api/v6"
	"github.com/fluxcd/flux/pkg/api/v9"
	"github.com/fluxcd/flux/pkg/cluster"
	"github.com/fluxcd/flux/pkg/cluster/kubernetes"
	"github.com/fluxcd/flux/pkg/cluster/kubernetes/testfiles"
	"github.com/fluxcd/flux/pkg/cluster/mock"
	"github.com/fluxcd/flux/pkg/event"
	"github.com/fluxcd/flux/pkg/git"
	"github.com/fluxcd/flux/pkg/git/gittest"
	"github.com/fluxcd/flux/pkg/image"
	"github.com/fluxcd/flux/pkg/job"
	"github.com/fluxcd/flux/pkg/manifests"
	"github.com/fluxcd/flux/pkg/policy"
	"github.com/fluxcd/flux/pkg/registry"
	registryMock "github.com/fluxcd/flux/pkg/registry/mock"
	"github.com/fluxcd/flux/pkg/resource"
	fluxsync "github.com/fluxcd/flux/pkg/sync"
	"github.com/fluxcd/flux/pkg/update"
)
const (
	wl                = "default:deployment/helloworld"
	container         = "greeter"
	ns                = "default"
	oldHelloImage     = "quay.io/weaveworks/helloworld:3"  
	newHelloImage     = "quay.io/weaveworks/helloworld:2"
	currentHelloImage = "quay.io/weaveworks/helloworld:master-a000001"
	anotherWl        = "another:deployment/service"
	anotherContainer = "it-doesn't-matter"
	anotherImage     = "another/service:latest"
	invalidNS   = "adsajkfldsa"
	testVersion = "test"
)
var testBytes = []byte(`{}`)
const timeout = 10 * time.Second
func TestDaemon_Ping(t *testing.T) {
	d, start, clean, _, _, _ := mockDaemon(t)
	start()
	defer clean()
	ctx := context.Background()
	if d.Ping(ctx) != nil {
		t.Fatal("Cluster did not return valid nil ping")
	}
}
func TestDaemon_Version(t *testing.T) {
	d, start, clean, _, _, _ := mockDaemon(t)
	start()
	defer clean()
	ctx := context.Background()
	v, err := d.Version(ctx)
	if err != nil {
		t.Fatalf("Error: %s", err.Error())
	}
	if v != testVersion {
		t.Fatalf("Expected %v but got %v", testVersion, v)
	}
}
func TestDaemon_Export(t *testing.T) {
	d, start, clean, _, _, _ := mockDaemon(t)
	start()
	defer clean()
	ctx := context.Background()
	bytes, err := d.Export(ctx)
	if err != nil {
		t.Fatalf("Error: %s", err.Error())
	}
	if string(testBytes) != string(bytes) {
		t.Fatalf("Expected %v but got %v", string(testBytes), string(bytes))
	}
}
func TestDaemon_ListWorkloads(t *testing.T) {
	d, start, clean, _, _, _ := mockDaemon(t)
	start()
	defer clean()
	ctx := context.Background()
	s, err := d.ListServices(ctx, "")
	if err != nil {
		t.Fatalf("Error: %s", err.Error())
	}
	if len(s) != 2 {
		t.Fatalf("Expected %v but got %v", 2, len(s))
	}
	s, err = d.ListServices(ctx, ns)
	if err != nil {
		t.Fatalf("Error: %s", err.Error())
	}
	if 1 != len(s) {
		t.Fatalf("Expected %v but got %v", 1, len(s))
	}
	s, err = d.ListServices(ctx, invalidNS)
	if err != nil {
		t.Fatalf("Error: %s", err.Error())
	}
	if len(s) != 0 {
		t.Fatalf("Expected %v but got %v", 0, len(s))
	}
}
func TestDaemon_ListWorkloadsWithOptions(t *testing.T) {
	d, start, clean, _, _, _ := mockDaemon(t)
	start()
	defer clean()
	ctx := context.Background()
	t.Run("no filter", func(t *testing.T) {
		s, err := d.ListServicesWithOptions(ctx, v11.ListServicesOptions{})
		if err != nil {
			t.Fatalf("Error: %s", err.Error())
		}
		if len(s) != 2 {
			t.Fatalf("Expected %v but got %v", 2, len(s))
		}
	})
	t.Run("filter id", func(t *testing.T) {
		s, err := d.ListServicesWithOptions(ctx, v11.ListServicesOptions{
			Namespace: "",
			Services:  []resource.ID{resource.MustParseID(wl)}})
		if err != nil {
			t.Fatalf("Error: %s", err.Error())
		}
		if len(s) != 1 {
			t.Fatalf("Expected %v but got %v", 1, len(s))
		}
	})
	t.Run("filter id and namespace", func(t *testing.T) {
		_, err := d.ListServicesWithOptions(ctx, v11.ListServicesOptions{
			Namespace: "foo",
			Services:  []resource.ID{resource.MustParseID(wl)}})
		if err == nil {
			t.Fatal("Expected error but got nil")
		}
	})
	t.Run("filter unsupported id kind", func(t *testing.T) {
		_, err := d.ListServicesWithOptions(ctx, v11.ListServicesOptions{
			Namespace: "foo",
			Services:  []resource.ID{resource.MustParseID("default:unsupportedkind/goodbyeworld")}})
		if err == nil {
			t.Fatal("Expected error but got nil")
		}
	})
}
func TestDaemon_ListImagesWithOptions(t *testing.T) {
	d, start, clean, _, _, _ := mockDaemon(t)
	start()
	defer clean()
	ctx := context.Background()
	specAll := update.ResourceSpec(update.ResourceSpecAll)
	svcID, err := resource.ParseID(wl)
	assert.NoError(t, err)
	currentImageRef, err := image.ParseRef(currentHelloImage)
	assert.NoError(t, err)
	newImageRef, err := image.ParseRef(newHelloImage)
	assert.NoError(t, err)
	oldImageRef, err := image.ParseRef(oldHelloImage)
	assert.NoError(t, err)
	anotherSvcID, err := resource.ParseID(anotherWl)
	assert.NoError(t, err)
	anotherImageRef, err := image.ParseRef(anotherImage)
	assert.NoError(t, err)
	tests := []struct {
		name string
		opts v10.ListImagesOptions
		expectedImages    []v6.ImageStatus
		expectedNumImages int
		shouldError       bool
	}{
		{
			name: "All services",
			opts: v10.ListImagesOptions{Spec: specAll},
			expectedImages: []v6.ImageStatus{
				{
					ID: svcID,
					Containers: []v6.Container{
						{
							Name:           container,
							Current:        image.Info{ID: currentImageRef},
							LatestFiltered: image.Info{ID: newImageRef},
							Available: []image.Info{
								{ID: newImageRef},
								{ID: currentImageRef},
								{ID: oldImageRef},
							},
							AvailableImagesCount:    3,
							NewAvailableImagesCount: 1,
							FilteredImagesCount:     3,
							NewFilteredImagesCount:  1,
						},
					},
				},
				{
					ID: anotherSvcID,
					Containers: []v6.Container{
						{
							Name:           anotherContainer,
							Current:        image.Info{ID: anotherImageRef},
							LatestFiltered: image.Info{},
							Available: []image.Info{
								{ID: anotherImageRef},
							},
							AvailableImagesCount:    1,
							NewAvailableImagesCount: 0,
							FilteredImagesCount:     0,  
							NewFilteredImagesCount:  0,
						},
					},
				},
			},
			shouldError: false,
		},
		{
			name: "Specific service",
			opts: v10.ListImagesOptions{Spec: update.ResourceSpec(wl)},
			expectedImages: []v6.ImageStatus{
				{
					ID: svcID,
					Containers: []v6.Container{
						{
							Name:           container,
							Current:        image.Info{ID: currentImageRef},
							LatestFiltered: image.Info{ID: newImageRef},
							Available: []image.Info{
								{ID: newImageRef},
								{ID: currentImageRef},
								{ID: oldImageRef},
							},
							AvailableImagesCount:    3,
							NewAvailableImagesCount: 1,
							FilteredImagesCount:     3,
							NewFilteredImagesCount:  1,
						},
					},
				},
			},
			shouldError: false,
		},
		{
			name: "Override container field selection",
			opts: v10.ListImagesOptions{
				Spec:                    specAll,
				OverrideContainerFields: []string{"Name", "Current", "NewAvailableImagesCount"},
			},
			expectedImages: []v6.ImageStatus{
				{
					ID: svcID,
					Containers: []v6.Container{
						{
							Name:                    container,
							Current:                 image.Info{ID: currentImageRef},
							NewAvailableImagesCount: 1,
						},
					},
				},
				{
					ID: anotherSvcID,
					Containers: []v6.Container{
						{
							Name:                    anotherContainer,
							Current:                 image.Info{ID: anotherImageRef},
							NewAvailableImagesCount: 0,
						},
					},
				},
			},
			shouldError: false,
		},
		{
			name: "Override container field selection with invalid field",
			opts: v10.ListImagesOptions{
				Spec:                    specAll,
				OverrideContainerFields: []string{"InvalidField"},
			},
			expectedImages: nil,
			shouldError:    true,
		},
		{
			name: "Specific namespace",
			opts: v10.ListImagesOptions{
				Spec:      specAll,
				Namespace: ns,
			},
			expectedImages: []v6.ImageStatus{
				{
					ID: svcID,
					Containers: []v6.Container{
						{
							Name:           container,
							Current:        image.Info{ID: currentImageRef},
							LatestFiltered: image.Info{ID: newImageRef},
							Available: []image.Info{
								{ID: newImageRef},
								{ID: currentImageRef},
								{ID: oldImageRef},
							},
							AvailableImagesCount:    3,
							NewAvailableImagesCount: 1,
							FilteredImagesCount:     3,
							NewFilteredImagesCount:  1,
						},
					},
				},
			},
			shouldError: false,
		},
		{
			name: "Specific namespace and service",
			opts: v10.ListImagesOptions{
				Spec:      update.ResourceSpec(wl),
				Namespace: ns,
			},
			expectedImages: nil,
			shouldError:    true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			is, err := d.ListImagesWithOptions(ctx, tt.opts)
			assert.Equal(t, tt.shouldError, err != nil)
			for ri, r := range is {
				for ci, c := range r.Containers {
					is[ri].Containers[ci].Current.CreatedAt = time.Time{}
					is[ri].Containers[ci].LatestFiltered.CreatedAt = time.Time{}
					for ai := range c.Available {
						is[ri].Containers[ci].Available[ai].CreatedAt = time.Time{}
					}
				}
			}
			assert.Equal(t, tt.expectedImages, is)
		})
	}
}
func TestDaemon_NotifyChange(t *testing.T) {
	d, start, clean, mockK8s, events, _ := mockDaemon(t)
	w := newWait(t)
	ctx := context.Background()
	var syncCalled int
	var syncDef *cluster.SyncSet
	var syncMu sync.Mutex
	mockK8s.SyncFunc = func(def cluster.SyncSet) error {
		syncMu.Lock()
		syncCalled++
		syncDef = &def
		syncMu.Unlock()
		return nil
	}
	start()
	defer clean()
	d.NotifyChange(ctx, v9.Change{Kind: v9.GitChange, Source: v9.GitUpdate{}})
	w.Eventually(func() bool {
		syncMu.Lock()
		defer syncMu.Unlock()
		return syncCalled == 1
	}, "Waiting for sync called")
	syncMu.Lock()
	defer syncMu.Unlock()
	if syncCalled != 1 {
		t.Errorf("Sync was not called once, was called %d times", syncCalled)
	} else if syncDef == nil {
		t.Errorf("Sync was called with a nil syncDef")
	}
	w.Eventually(func() bool {
		es, _ := events.AllEvents(time.Time{}, -1, time.Time{})
		for _, e := range es {
			if e.Type == event.EventSync {
				return true
			}
		}
		return false
	}, "Waiting for new sync events")
}