// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package controller_test

import (
	"context"
	"errors"
	"fmt"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing"
	"github.com/hashicorp/consul/internal/controller"
	"github.com/hashicorp/consul/internal/controller/cache"
	"github.com/hashicorp/consul/internal/controller/cache/index"
	"github.com/hashicorp/consul/internal/controller/cache/indexers"
	"github.com/hashicorp/consul/internal/controller/dependency"
	"github.com/hashicorp/consul/internal/resource"
	"github.com/hashicorp/consul/internal/resource/demo"
	"github.com/hashicorp/consul/internal/resource/resourcetest"
	"github.com/hashicorp/consul/proto-public/pbresource"
	pbdemov1 "github.com/hashicorp/consul/proto/private/pbdemo/v1"
	pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
	"github.com/hashicorp/consul/proto/private/prototest"
	"github.com/hashicorp/consul/sdk/testutil"
	"github.com/hashicorp/consul/sdk/testutil/retry"
)

var injectedError = errors.New("injected error")

func errQuery(_ cache.ReadOnlyCache, _ ...any) (cache.ResourceIterator, error) {
	return nil, injectedError
}

func TestController_API(t *testing.T) {
	t.Parallel()

	idx := indexers.DecodedSingleIndexer("genre", index.SingleValueFromArgs(func(value string) ([]byte, error) {
		var b index.Builder
		b.String(value)
		return b.Bytes(), nil
	}), func(res *resource.DecodedResource[*pbdemov2.Artist]) (bool, []byte, error) {
		var b index.Builder
		b.String(res.Data.Genre.String())
		return true, b.Bytes(), nil
	})

	rec := newTestReconciler()
	init := newTestInitializer(1)
	client := svctest.NewResourceServiceBuilder().
		WithRegisterFns(demo.RegisterTypes).
		Run(t)

	concertsChan := make(chan controller.Event)
	defer close(concertsChan)
	concertSource := &controller.Source{Source: concertsChan}
	concertMapper := func(ctx context.Context, rt controller.Runtime, event controller.Event) ([]controller.Request, error) {
		artistID := event.Obj.(*Concert).artistID
		var requests []controller.Request
		requests = append(requests, controller.Request{ID: artistID})
		return requests, nil
	}

	ctrl := controller.
		NewController("artist", pbdemov2.ArtistType, idx).
		WithWatch(pbdemov2.AlbumType, dependency.MapOwner, indexers.OwnerIndex("owner")).
		WithQuery("some-query", errQuery).
		WithCustomWatch(concertSource, concertMapper).
		WithBackoff(10*time.Millisecond, 100*time.Millisecond).
		WithReconciler(rec).
		WithInitializer(init)

	mgr := controller.NewManager(client, testutil.Logger(t))
	mgr.Register(ctrl)
	mgr.SetRaftLeader(true)
	go mgr.Run(testContext(t))

	// Wait for initialization to complete
	init.wait(t)

	t.Run("managed resource type", func(t *testing.T) {
		res, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)

		rt, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

		// ensure that the cache index is being properly managed
		dec := resourcetest.MustDecode[*pbdemov2.Artist](t, res)
		resources, err := rt.Cache.List(pbdemov2.ArtistType, "genre", dec.Data.Genre.String())
		require.NoError(t, err)
		prototest.AssertElementsMatch(t, []*pbresource.Resource{rsp.Resource}, resources)

		// ensure that the query was successfully registered - as we should not do equality
		// checks on functions we are using a constant error return query to ensure it was
		// registered properly.
		iter, err := rt.Cache.Query("some-query", "irrelevant")
		require.ErrorIs(t, err, injectedError)
		require.Nil(t, iter)
	})

	t.Run("watched resource type", func(t *testing.T) {
		res, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)

		_, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

		rec.expectNoRequest(t, 500*time.Millisecond)

		album, err := demo.GenerateV2Album(rsp.Resource.Id)
		require.NoError(t, err)

		albumRsp1, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: album})
		require.NoError(t, err)

		_, req = rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

		album2, err := demo.GenerateV2Album(rsp.Resource.Id)
		require.NoError(t, err)

		albumRsp2, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: album2})
		require.NoError(t, err)

		rt, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

		// ensure that the watched type cache is being updated
		resources, err := rt.Cache.List(pbdemov2.AlbumType, "owner", rsp.Resource.Id)
		require.NoError(t, err)
		prototest.AssertElementsMatch(t, []*pbresource.Resource{albumRsp1.Resource, albumRsp2.Resource}, resources)
	})

	t.Run("custom watched resource type", func(t *testing.T) {
		res, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)

		_, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

		rec.expectNoRequest(t, 500*time.Millisecond)

		concertsChan <- controller.Event{Obj: &Concert{name: "test-concert", artistID: rsp.Resource.Id}}

		_, watchedReq := rec.wait(t)
		prototest.AssertDeepEqual(t, req.ID, watchedReq.ID)

		otherArtist, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		concertsChan <- controller.Event{Obj: &Concert{name: "test-concert", artistID: otherArtist.Id}}

		_, watchedReq = rec.wait(t)
		prototest.AssertDeepEqual(t, otherArtist.Id, watchedReq.ID)
	})

	t.Run("error retries", func(t *testing.T) {
		rec.failNext(errors.New("KABOOM"))

		res, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)

		_, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

		// Reconciler should be called with the same request again.
		_, req = rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)
	})

	t.Run("panic retries", func(t *testing.T) {
		rec.panicNext("KABOOM")

		res, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)

		_, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

		// Reconciler should be called with the same request again.
		_, req = rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)
	})

	t.Run("defer", func(t *testing.T) {
		rec.failNext(controller.RequeueAfter(1 * time.Second))

		res, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)

		_, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

		rec.expectNoRequest(t, 750*time.Millisecond)

		_, req = rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)
	})
}

func TestController_API_InitializeRetry(t *testing.T) {
	t.Parallel()

	// Configure initializer to error initially in order to test retries
	expectedInitAttempts := 2
	init := newTestInitializer(expectedInitAttempts)

	client := svctest.NewResourceServiceBuilder().
		WithRegisterFns(demo.RegisterTypes).
		Run(t)
	rec := newTestReconciler()

	ctrl := controller.
		NewController("artist", pbdemov2.ArtistType).
		WithBackoff(10*time.Millisecond, 100*time.Millisecond).
		WithReconciler(rec).
		WithInitializer(init)

	mgr := controller.NewManager(client, testutil.Logger(t))
	mgr.Register(ctrl)
	mgr.SetRaftLeader(true)
	go mgr.Run(testContext(t))

	// Wait for initialization attempts to complete
	for i := 0; i < expectedInitAttempts; i++ {
		init.wait(t)
	}

	// Create a resource and expect it to reconcile now that initialization is complete
	res, err := demo.GenerateV2Artist()
	require.NoError(t, err)

	_, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
	require.NoError(t, err)

	rec.wait(t)
}

func waitForAtomicBoolValue(t testutil.TestingTB, actual *atomic.Bool, expected bool) {
	t.Helper()
	retry.Run(t, func(r *retry.R) {
		require.Equal(r, expected, actual.Load())
	})
}

func TestController_WithForceReconcileEvery_UpsertSuccess(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}
	t.Parallel()

	// Given a controller
	// When the controller reconciles a resource due to an upsert and succeeds
	// Then the controller manager should scheduled a forced reconcile after forceReconcileEvery
	rec := newTestReconciler()
	client := svctest.NewResourceServiceBuilder().
		WithRegisterFns(demo.RegisterTypes).
		Run(t)

	// Create sizeable gap between reconcile #1 and forced reconcile #2 to ensure the delay occurs
	forceReconcileEvery := 5 * time.Second
	ctrl := controller.
		NewController("artist", pbdemov2.ArtistType).
		WithLogger(testutil.Logger(t)).
		WithForceReconcileEvery(forceReconcileEvery).
		WithReconciler(rec)

	mgr := controller.NewManager(client, testutil.Logger(t))
	mgr.Register(ctrl)
	mgr.SetRaftLeader(true)
	go mgr.Run(testContext(t))

	res, err := demo.GenerateV2Artist()
	require.NoError(t, err)

	rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
	require.NoError(t, err)

	// Verify reconcile #1 happens immediately
	_, req := rec.waitFor(t, time.Second)
	prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

	// Verify no reconciles occur between reconcile #1 and forced reconcile #2.
	// Remove a second for max jitter (20% of 5s) and one more second to be safe.
	rec.expectNoRequest(t, forceReconcileEvery-time.Second-time.Second)

	// Verify forced reconcile #2 occurred (forceReconcileEvery - 1s - 1s + 3s > forceReconcileEvery)
	_, req = rec.waitFor(t, time.Second*3)
	prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)
}

func TestController_WithForceReconcileEvery_SkipOnReconcileError(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}
	t.Parallel()

	// Given a controller configured with a forceReconcileEvery duration
	// When the controller reconciles a resource due to an upsert and returns an error
	// Then the controller manager should not schedule a forced reconcile and allow
	//      the existing error handling to schedule a rate-limited retry
	rec := newTestReconciler()
	client := svctest.NewResourceServiceBuilder().
		WithRegisterFns(demo.RegisterTypes).
		Run(t)

	// Large enough gap to test for a period of no-reconciles
	forceReconcileEvery := 5 * time.Second
	ctrl := controller.
		NewController("artist", pbdemov2.ArtistType).
		WithLogger(testutil.Logger(t)).
		WithForceReconcileEvery(forceReconcileEvery).
		WithReconciler(rec)

	mgr := controller.NewManager(client, testutil.Logger(t))
	mgr.Register(ctrl)
	mgr.SetRaftLeader(true)
	go mgr.Run(testContext(t))

	res, err := demo.GenerateV2Artist()
	require.NoError(t, err)

	// Setup reconcile #1 to fail
	rec.failNext(errors.New("reconcile #1 error"))
	rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
	require.NoError(t, err)

	// Observe failed reconcile #1
	_, req := rec.wait(t)
	prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

	// Observe successful (rate-limited retry) reconcile #2. By not failNext'ing it,
	// we're expecting it now'ish.
	_, req = rec.wait(t)
	prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

	// Observe no forced reconcile for gap after last successful reconcile
	// -1s for 20% jitter reduction
	// -1s for just to be safe
	rec.expectNoRequest(t, forceReconcileEvery-time.Second-time.Second)

	// Finally observe forced reconcile #3 up to 1 sec past (5-1-1+3) accumulated forceReconcileEvery delay
	_, req = rec.waitFor(t, 3*time.Second)
	prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)
}

func TestController_WithForceReconcileEvery_SkipOnDelete(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}
	t.Parallel()
	// Given a controller configured with a forceReconcileEvery duration
	// When the controller reconciles a resource due to a delete and succeeds
	// Then the controller manager should not schedule a forced reconcile
	rec := newTestReconciler()
	client := svctest.NewResourceServiceBuilder().
		WithRegisterFns(demo.RegisterTypes).
		Run(t)

	// Large enough gap to test for a period of no-reconciles
	forceReconcileEvery := 5 * time.Second
	ctrl := controller.
		NewController("artist", pbdemov2.ArtistType).
		WithLogger(testutil.Logger(t)).
		WithForceReconcileEvery(forceReconcileEvery).
		WithReconciler(rec)

	mgr := controller.NewManager(client, testutil.Logger(t))
	mgr.Register(ctrl)
	mgr.SetRaftLeader(true)
	go mgr.Run(testContext(t))

	res, err := demo.GenerateV2Artist()
	require.NoError(t, err)

	rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
	require.NoError(t, err)

	// Account for reconcile #1 due to initial write
	_, req := rec.wait(t)
	prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)

	// Perform a delete
	_, err = client.Delete(testContext(t), &pbresource.DeleteRequest{Id: rsp.Resource.Id})
	require.NoError(t, err)

	// Account for the reconcile #2 due to the delete
	_, req = rec.wait(t)

	// Account for the deferred forced reconcile #3 from the original write event since deferred
	// reconciles don't seem to be de-duped against non-deferred reconciles.
	_, req = rec.waitFor(t, forceReconcileEvery)

	// Verify no further reconciles occur
	rec.expectNoRequest(t, forceReconcileEvery)
}

func TestController_Placement(t *testing.T) {
	t.Parallel()

	t.Run("singleton", func(t *testing.T) {
		var running atomic.Bool
		running.Store(false)

		rec := newTestReconciler()
		client := svctest.NewResourceServiceBuilder().
			WithRegisterFns(demo.RegisterTypes).
			WithCloningDisabled().
			Run(t)

		ctrl := controller.
			NewController("artist", pbdemov2.ArtistType).
			WithWatch(pbdemov2.AlbumType, dependency.MapOwner).
			WithPlacement(controller.PlacementSingleton).
			WithNotifyStart(func(context.Context, controller.Runtime) {
				running.Store(true)
			}).
			WithNotifyStop(func(context.Context, controller.Runtime) {
				running.Store(false)
			}).
			WithReconciler(rec)

		mgr := controller.NewManager(client, testutil.Logger(t))
		mgr.Register(ctrl)
		go mgr.Run(testContext(t))

		res, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		// Reconciler should not be called until we're the Raft leader.
		_, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)
		rec.expectNoRequest(t, 500*time.Millisecond)

		// Become the leader and check the reconciler is called.
		mgr.SetRaftLeader(true)
		waitForAtomicBoolValue(t, &running, true)
		_, _ = rec.wait(t)

		// Should not be called after losing leadership.
		mgr.SetRaftLeader(false)
		waitForAtomicBoolValue(t, &running, false)
		_, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)
		rec.expectNoRequest(t, 500*time.Millisecond)
	})

	t.Run("each server", func(t *testing.T) {
		var running atomic.Bool
		running.Store(false)

		rec := newTestReconciler()
		client := svctest.NewResourceServiceBuilder().
			WithRegisterFns(demo.RegisterTypes).
			Run(t)

		ctrl := controller.
			NewController("artist", pbdemov2.ArtistType).
			WithWatch(pbdemov2.AlbumType, dependency.MapOwner).
			WithPlacement(controller.PlacementEachServer).
			WithNotifyStart(func(context.Context, controller.Runtime) {
				running.Store(true)
			}).
			WithReconciler(rec)

		mgr := controller.NewManager(client, testutil.Logger(t))
		mgr.Register(ctrl)
		go mgr.Run(testContext(t))
		waitForAtomicBoolValue(t, &running, true)

		res, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		// Reconciler should be called even though we're not the Raft leader.
		_, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)
		_, _ = rec.wait(t)
	})
}

func TestController_String(t *testing.T) {
	ctrl := controller.
		NewController("artist", pbdemov2.ArtistType).
		WithWatch(pbdemov2.AlbumType, dependency.MapOwner).
		WithBackoff(5*time.Second, 1*time.Hour).
		WithPlacement(controller.PlacementEachServer)

	require.Equal(t,
		`<Controller managed_type=demo.v2.Artist, watched_types=[demo.v2.Album], backoff=<base=5s, max=1h0m0s>, placement=each-server>`,
		ctrl.String(),
	)
}

func TestController_NoReconciler(t *testing.T) {
	client := svctest.NewResourceServiceBuilder().
		WithRegisterFns(demo.RegisterTypes).
		Run(t)

	mgr := controller.NewManager(client, testutil.Logger(t))

	ctrl := controller.NewController("artist", pbdemov2.ArtistType)
	require.PanicsWithValue(t,
		fmt.Sprintf("cannot register controller without a reconciler %s", ctrl.String()),
		func() { mgr.Register(ctrl) })
}

func TestController_Watch(t *testing.T) {
	t.Parallel()

	t.Run("partitioned scoped resources", func(t *testing.T) {
		rec := newTestReconciler()

		client := svctest.NewResourceServiceBuilder().
			WithRegisterFns(demo.RegisterTypes).
			Run(t)

		ctrl := controller.
			NewController("labels", pbdemov1.RecordLabelType).
			WithReconciler(rec)

		mgr := controller.NewManager(client, testutil.Logger(t))
		mgr.SetRaftLeader(true)
		mgr.Register(ctrl)

		ctx := testContext(t)
		go mgr.Run(ctx)

		res, err := demo.GenerateV1RecordLabel("test")
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
		require.NoError(t, err)

		_, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)
	})

	t.Run("cluster scoped resources", func(t *testing.T) {
		rec := newTestReconciler()

		client := svctest.NewResourceServiceBuilder().
			WithRegisterFns(demo.RegisterTypes).
			Run(t)

		ctrl := controller.
			NewController("executives", pbdemov1.ExecutiveType).
			WithReconciler(rec)

		mgr := controller.NewManager(client, testutil.Logger(t))
		mgr.SetRaftLeader(true)
		mgr.Register(ctrl)

		go mgr.Run(testContext(t))

		exec, err := demo.GenerateV1Executive("test", "CEO")
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: exec})
		require.NoError(t, err)

		_, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)
	})

	t.Run("namespace scoped resources", func(t *testing.T) {
		rec := newTestReconciler()

		client := svctest.NewResourceServiceBuilder().
			WithRegisterFns(demo.RegisterTypes).
			Run(t)

		ctrl := controller.
			NewController("artists", pbdemov2.ArtistType).
			WithReconciler(rec)

		mgr := controller.NewManager(client, testutil.Logger(t))
		mgr.SetRaftLeader(true)
		mgr.Register(ctrl)

		go mgr.Run(testContext(t))

		artist, err := demo.GenerateV2Artist()
		require.NoError(t, err)

		rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: artist})
		require.NoError(t, err)

		_, req := rec.wait(t)
		prototest.AssertDeepEqual(t, rsp.Resource.Id, req.ID)
	})
}

func newTestReconciler() *testReconciler {
	return &testReconciler{
		calls:  make(chan requestArgs),
		errors: make(chan error, 1),
		panics: make(chan any, 1),
	}
}

type requestArgs struct {
	req controller.Request
	rt  controller.Runtime
}

type testReconciler struct {
	calls  chan requestArgs
	errors chan error
	panics chan any
}

func (r *testReconciler) Reconcile(_ context.Context, rt controller.Runtime, req controller.Request) error {
	r.calls <- requestArgs{req: req, rt: rt}

	select {
	case err := <-r.errors:
		return err
	case p := <-r.panics:
		panic(p)
	default:
		return nil
	}
}

func (r *testReconciler) failNext(err error) {
	r.errors <- err
}

func (r *testReconciler) panicNext(p any) {
	r.panics <- p
}

func (r *testReconciler) expectNoRequest(t *testing.T, duration time.Duration) {
	t.Helper()

	started := time.Now()
	select {
	case args := <-r.calls:
		t.Fatalf("expected no request for %s, but got: %s after %s", duration, args.req.ID, time.Since(started))
	case <-time.After(duration):
	}
}

func (r *testReconciler) wait(t *testing.T) (controller.Runtime, controller.Request) {
	t.Helper()
	return r.waitFor(t, 500*time.Millisecond)
}

func (r *testReconciler) waitFor(t *testing.T, duration time.Duration) (controller.Runtime, controller.Request) {
	t.Helper()

	var args requestArgs
	select {
	case args = <-r.calls:
	case <-time.After(duration):
		t.Fatalf("Reconcile was not called after %v", duration)
	}
	return args.rt, args.req
}

func testContext(t *testing.T) context.Context {
	t.Helper()

	ctx, cancel := context.WithCancel(context.Background())
	t.Cleanup(cancel)

	return ctx
}

type Concert struct {
	name     string
	artistID *pbresource.ID
}

func (c Concert) Key() string {
	return c.name
}

func newTestInitializer(errorCount int) *testInitializer {
	return &testInitializer{
		calls:            make(chan error, 1),
		expectedAttempts: errorCount,
	}
}

type testInitializer struct {
	expectedAttempts int // number of times the initializer should run to test retries
	attempts         int // running count of times initialize is called
	calls            chan error
}

func (i *testInitializer) Initialize(_ context.Context, _ controller.Runtime) error {
	i.attempts++
	if i.attempts < i.expectedAttempts {
		// Return an error to cause a retry
		err := errors.New("initialization error")
		i.calls <- err
		return err
	} else {
		i.calls <- nil
		return nil
	}
}

func (i *testInitializer) wait(t *testing.T) {
	t.Helper()
	select {
	case err := <-i.calls:
		if err == nil {
			// Initialize did not error, no more calls should be expected
			close(i.calls)
		}
		return
	case <-time.After(1000 * time.Millisecond):
		t.Fatal("Initialize was not called after 1000ms")
	}
}
