package main

import (
	"context"
	"errors"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	runtimefake "sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	"testing"
)

func newClient(t *testing.T) client.Client {
	t.Helper()
	s := runtime.NewScheme()
	if err := corev1.AddToScheme(s); err != nil {
		t.Fatal(err)
	}
	return runtimefake.NewClientBuilder().WithScheme(s).WithObjects(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "demo"}, Data: map[string]string{"owner": "team"}}).Build()
}
func request(name string) reconcile.Request {
	return reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "demo", Name: name}}
}
func TestIdempotent(t *testing.T) {
	ctx := context.Background()
	c := &countingClient{Client: newClient(t)}
	r := Reconciler{Client: c}
	for range 2 {
		if _, err := r.Reconcile(ctx, request("app")); err != nil {
			t.Fatal(err)
		}
	}
	if c.updates != 1 {
		t.Fatal(c.updates)
	}
	var cm corev1.ConfigMap
	if err := c.Get(ctx, request("app").NamespacedName, &cm); err != nil {
		t.Fatal(err)
	}
	if cm.Data["managed"] != "true" || cm.Data["owner"] != "team" {
		t.Fatal(cm.Data)
	}
	if _, err := r.Reconcile(ctx, request("missing")); err != nil {
		t.Fatal(err)
	}
}

type conflictOnce struct {
	client.Client
	failed bool
}

func (c *conflictOnce) Update(ctx context.Context, o client.Object, opts ...client.UpdateOption) error {
	if !c.failed {
		c.failed = true
		return apierrors.NewConflict(schema.GroupResource{Resource: "configmaps"}, o.GetName(), errors.New("injected conflict"))
	}
	return c.Client.Update(ctx, o, opts...)
}
func TestConflictIsReturnedThenReread(t *testing.T) {
	ctx := context.Background()
	c := &conflictOnce{Client: newClient(t)}
	r := Reconciler{Client: c}
	if _, err := r.Reconcile(ctx, request("app")); !apierrors.IsConflict(err) {
		t.Fatalf("expected conflict: %v", err)
	}
	if _, err := r.Reconcile(ctx, request("app")); err != nil {
		t.Fatal(err)
	}
}
