github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/togglebutton/reconciler_test.go (about)

     1  package togglebutton
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     9  	"k8s.io/apimachinery/pkg/types"
    10  
    11  	"github.com/tilt-dev/tilt/internal/controllers/fake"
    12  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    13  )
    14  
    15  var tbName = types.NamespacedName{Name: "test-toggle"}
    16  
    17  func TestReconciler_CreatesOffUIButton(t *testing.T) {
    18  	f := newFixture(t)
    19  
    20  	f.setupTest()
    21  
    22  	uib := f.uiButton()
    23  	require.Equal(t, "enable", uib.Spec.Text)
    24  
    25  	f.requireSteadyState()
    26  }
    27  
    28  func TestReconciler_CreatesOnUIButton(t *testing.T) {
    29  	f := newFixture(t)
    30  
    31  	f.setupTest()
    32  
    33  	cm := f.configMap()
    34  	cm.Data["enabled"] = "true"
    35  	require.NoError(t, f.Client.Update(f.ctx, &cm))
    36  
    37  	f.MustReconcile(tbName)
    38  
    39  	uib := f.uiButton()
    40  	require.Equal(t, "disable", uib.Spec.Text)
    41  
    42  	f.requireSteadyState()
    43  }
    44  
    45  func TestReconciler_DeletesUIButton(t *testing.T) {
    46  	f := newFixture(t)
    47  
    48  	f.setupTest()
    49  
    50  	uib := f.uiButton()
    51  	require.NotNil(t, uib)
    52  
    53  	tb := f.toggleButton()
    54  	found, _ := f.Delete(&tb)
    55  	require.True(t, found)
    56  
    57  	found = f.Get(f.KeyForObject(&uib), &uib)
    58  	require.False(t, found)
    59  }
    60  
    61  func TestReconciler_HandlesClick(t *testing.T) {
    62  	f := newFixture(t)
    63  
    64  	f.setupTest()
    65  
    66  	// simulate a click on the button
    67  	uib := f.uiButton()
    68  	require.NotNil(t, uib)
    69  	uib.Status.LastClickedAt = metav1.NowMicro()
    70  	uib.Status.Inputs = append(uib.Status.Inputs, v1alpha1.UIInputStatus{
    71  		Name:   uib.Spec.Inputs[0].Name,
    72  		Hidden: &v1alpha1.UIHiddenInputStatus{Value: uib.Spec.Inputs[0].Hidden.Value},
    73  	})
    74  	err := f.Client.Status().Update(f.ctx, &uib)
    75  	require.NoError(t, err)
    76  
    77  	f.MustReconcile(tbName)
    78  
    79  	// now we should see:
    80  	// 1. The ConfigMap gets updated
    81  	cm := f.configMap()
    82  	require.Equal(t, "true", cm.Data["enabled"])
    83  
    84  	// 2. The UIButton reflects the TB's OnStateSpec
    85  	uib = f.uiButton()
    86  	require.Equal(t, "disable", uib.Spec.Text)
    87  
    88  	f.requireSteadyState()
    89  }
    90  
    91  func TestReconciler_HandlesConfigMapUpdate(t *testing.T) {
    92  	f := newFixture(t)
    93  
    94  	f.setupTest()
    95  
    96  	cm := f.configMap()
    97  	cm.Data["enabled"] = "true"
    98  	err := f.Client.Update(f.ctx, &cm)
    99  	require.NoError(t, err)
   100  
   101  	f.MustReconcile(tbName)
   102  
   103  	// changing the configmap directly should cause the uibutton to update
   104  	uib := f.uiButton()
   105  	require.Equal(t, "disable", uib.Spec.Text)
   106  
   107  	f.requireSteadyState()
   108  }
   109  
   110  func TestReconciler_uiButtonClickedNoInput(t *testing.T) {
   111  	f := newFixture(t)
   112  
   113  	f.setupTest()
   114  
   115  	// simulate a click on the button, but without the expected input
   116  	uib := f.uiButton()
   117  	require.NotNil(t, uib)
   118  	uib.Status.LastClickedAt = metav1.NowMicro()
   119  	err := f.Client.Status().Update(f.ctx, &uib)
   120  	require.NoError(t, err)
   121  
   122  	f.MustReconcile(tbName)
   123  
   124  	tb := f.toggleButton()
   125  	require.Contains(t, tb.Status.Error, "does not have an input named \"action\"")
   126  
   127  	f.requireSteadyState()
   128  }
   129  
   130  func TestReconciler_uiButtonClickedInputWrongType(t *testing.T) {
   131  	f := newFixture(t)
   132  
   133  	f.setupTest()
   134  
   135  	// simulate a click on the button, but with the wrong input type
   136  	uib := f.uiButton()
   137  	require.NotNil(t, uib)
   138  	uib.Status.LastClickedAt = metav1.NowMicro()
   139  	uib.Status.Inputs = []v1alpha1.UIInputStatus{
   140  		{
   141  			Name: actionUIInputName,
   142  			Text: &v1alpha1.UITextInputStatus{Value: turnOnInputValue},
   143  		},
   144  	}
   145  	err := f.Client.Status().Update(f.ctx, &uib)
   146  	require.NoError(t, err)
   147  
   148  	f.MustReconcile(tbName)
   149  
   150  	tb := f.toggleButton()
   151  	require.Contains(t, tb.Status.Error, "input \"action\" was not of type 'Hidden'")
   152  
   153  	f.requireSteadyState()
   154  }
   155  
   156  func TestReconciler_uiButtonClickedInputWrongValue(t *testing.T) {
   157  	f := newFixture(t)
   158  
   159  	f.setupTest()
   160  
   161  	// simulate a click on the button, but with an unknown value
   162  	uib := f.uiButton()
   163  	require.NotNil(t, uib)
   164  	uib.Status.LastClickedAt = metav1.NowMicro()
   165  	uib.Status.Inputs = []v1alpha1.UIInputStatus{
   166  		{
   167  			Name:   actionUIInputName,
   168  			Hidden: &v1alpha1.UIHiddenInputStatus{Value: "fdasfsa"},
   169  		},
   170  	}
   171  	err := f.Client.Status().Update(f.ctx, &uib)
   172  	require.NoError(t, err)
   173  
   174  	f.MustReconcile(tbName)
   175  
   176  	tb := f.toggleButton()
   177  	require.Contains(t, tb.Status.Error, "unexpected value \"fdasfsa\"")
   178  
   179  	f.requireSteadyState()
   180  }
   181  
   182  func TestReconciler_noConfigMap(t *testing.T) {
   183  	f := newFixture(t)
   184  
   185  	f.createToggleButton()
   186  
   187  	tb := f.toggleButton()
   188  	require.Equal(t, "no such ConfigMap \"toggle-cm\"", tb.Status.Error)
   189  
   190  	f.requireSteadyState()
   191  }
   192  
   193  func TestReconciler_doesntSpecifyConfigMap(t *testing.T) {
   194  	f := newFixture(t)
   195  
   196  	f.createToggleButton()
   197  	tb := f.toggleButton()
   198  	tb.Spec.StateSource.ConfigMap = nil
   199  	f.Update(&tb)
   200  
   201  	// simulate a click on the button, but with an unknown value
   202  	uib := f.uiButton()
   203  	require.NotNil(t, uib)
   204  	uib.Status.LastClickedAt = metav1.NowMicro()
   205  	uib.Status.Inputs = []v1alpha1.UIInputStatus{
   206  		{
   207  			Name:   actionUIInputName,
   208  			Hidden: &v1alpha1.UIHiddenInputStatus{Value: "foo"},
   209  		},
   210  	}
   211  	err := f.Client.Status().Update(f.ctx, &uib)
   212  	require.NoError(t, err)
   213  
   214  	f.MustReconcile(tbName)
   215  
   216  	tb = f.toggleButton()
   217  	require.Contains(t, tb.Status.Error, "Spec.StateSource.ConfigMap is nil")
   218  
   219  	f.requireSteadyState()
   220  }
   221  
   222  func TestReconciler_ConfigMapUnexpectedValue(t *testing.T) {
   223  	f := newFixture(t)
   224  	f.setupTest()
   225  
   226  	cm := f.configMap()
   227  	tb := f.toggleButton()
   228  	cm.Data[tb.Spec.StateSource.ConfigMap.Key] = "asdf"
   229  	err := f.Client.Update(f.ctx, &cm)
   230  	require.NoError(t, err)
   231  
   232  	f.MustReconcile(tbName)
   233  
   234  	tb = f.toggleButton()
   235  	require.Contains(t, tb.Status.Error, "unknown value \"asdf\"")
   236  
   237  	f.requireSteadyState()
   238  }
   239  
   240  func TestReconciler_clearError(t *testing.T) {
   241  	f := newFixture(t)
   242  	f.setupTest()
   243  
   244  	cm := f.configMap()
   245  	tb := f.toggleButton()
   246  	cm.Data[tb.Spec.StateSource.ConfigMap.Key] = "asdf"
   247  	err := f.Client.Status().Update(f.ctx, &cm)
   248  	require.NoError(t, err)
   249  	f.MustReconcile(tbName)
   250  
   251  	cm.Data[tb.Spec.StateSource.ConfigMap.Key] = tb.Spec.StateSource.ConfigMap.OnValue
   252  	err = f.Client.Status().Update(f.ctx, &cm)
   253  	require.NoError(t, err)
   254  	f.MustReconcile(tbName)
   255  
   256  	tb = f.toggleButton()
   257  	require.Equal(t, "", tb.Status.Error)
   258  
   259  	f.requireSteadyState()
   260  }
   261  
   262  type fixture struct {
   263  	*fake.ControllerFixture
   264  	t   *testing.T
   265  	ctx context.Context
   266  }
   267  
   268  func newFixture(t *testing.T) *fixture {
   269  	cfb := fake.NewControllerFixtureBuilder(t)
   270  	r := NewReconciler(cfb.Client, cfb.Client.Scheme())
   271  	return &fixture{
   272  		ControllerFixture: cfb.Build(r),
   273  		t:                 t,
   274  		ctx:               context.Background(),
   275  	}
   276  }
   277  
   278  func (f *fixture) createConfigMap() {
   279  	err := f.Client.Create(f.ctx, &v1alpha1.ConfigMap{
   280  		ObjectMeta: metav1.ObjectMeta{
   281  			Name: "toggle-cm",
   282  		},
   283  		Data: map[string]string{
   284  			"enabled": "false",
   285  		},
   286  	})
   287  	require.NoError(f.t, err)
   288  }
   289  
   290  func (f *fixture) createToggleButton() {
   291  	err := f.Client.Create(f.ctx, &v1alpha1.ToggleButton{
   292  		ObjectMeta: metav1.ObjectMeta{
   293  			Name:      tbName.Name,
   294  			Namespace: tbName.Namespace,
   295  		},
   296  		Spec: v1alpha1.ToggleButtonSpec{
   297  			On: v1alpha1.ToggleButtonStateSpec{
   298  				Text:     "disable",
   299  				IconName: "stop",
   300  			},
   301  			Off: v1alpha1.ToggleButtonStateSpec{
   302  				Text:     "enable",
   303  				IconName: "play_arrow",
   304  			},
   305  			DefaultOn: false,
   306  			StateSource: v1alpha1.StateSource{
   307  				ConfigMap: &v1alpha1.ConfigMapStateSource{
   308  					Name:     "toggle-cm",
   309  					Key:      "enabled",
   310  					OnValue:  "true",
   311  					OffValue: "false",
   312  				},
   313  			},
   314  		},
   315  	})
   316  	require.NoError(f.t, err)
   317  	f.MustReconcile(tbName)
   318  }
   319  
   320  func (f *fixture) setupTest() {
   321  	f.createConfigMap()
   322  	f.createToggleButton()
   323  }
   324  
   325  func (f *fixture) toggleButton() v1alpha1.ToggleButton {
   326  	var tb v1alpha1.ToggleButton
   327  	f.MustGet(tbName, &tb)
   328  	return tb
   329  }
   330  
   331  func (f *fixture) uiButton() v1alpha1.UIButton {
   332  	var uib v1alpha1.UIButton
   333  	f.MustGet(types.NamespacedName{Name: "toggle-test-toggle"}, &uib)
   334  	return uib
   335  }
   336  
   337  func (f *fixture) configMap() v1alpha1.ConfigMap {
   338  	var cm v1alpha1.ConfigMap
   339  	f.MustGet(types.NamespacedName{Name: "toggle-cm"}, &cm)
   340  	return cm
   341  }
   342  
   343  // ensures a reconcile doesn't cause any object changes and thus risk infinitely reconciling
   344  func (f *fixture) requireSteadyState() {
   345  	tb := f.toggleButton()
   346  	f.MustReconcile(tbName)
   347  	tb2 := f.toggleButton()
   348  	require.Equal(f.t, tb.ObjectMeta.ResourceVersion, tb2.ObjectMeta.ResourceVersion)
   349  }