github.com/kubeshop/testkube@v1.17.23/pkg/triggers/matcher_test.go (about)

     1  package triggers
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"net/url"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  
    13  	testtriggersv1 "github.com/kubeshop/testkube-operator/api/testtriggers/v1"
    14  	"github.com/kubeshop/testkube/pkg/log"
    15  )
    16  
    17  func TestService_matchConditionsRetry(t *testing.T) {
    18  	t.Parallel()
    19  
    20  	retry := 0
    21  	e := &watcherEvent{
    22  		resource:  "deployment",
    23  		name:      "test-deployment",
    24  		namespace: "testkube",
    25  		labels:    nil,
    26  		object:    nil,
    27  		eventType: "modified",
    28  		causes:    nil,
    29  		conditionsGetter: func() ([]testtriggersv1.TestTriggerCondition, error) {
    30  			retry++
    31  			status := testtriggersv1.FALSE_TestTriggerConditionStatuses
    32  			if retry == 1 {
    33  				status = testtriggersv1.TRUE_TestTriggerConditionStatuses
    34  			}
    35  
    36  			return []testtriggersv1.TestTriggerCondition{
    37  				{
    38  					Type_:  "Progressing",
    39  					Status: &status,
    40  					Reason: "NewReplicaSetAvailable",
    41  					Ttl:    60,
    42  				},
    43  				{
    44  					Type_:  "Available",
    45  					Status: &status,
    46  				},
    47  			}, nil
    48  		},
    49  	}
    50  
    51  	var timeout int32 = 1
    52  	status := testtriggersv1.TRUE_TestTriggerConditionStatuses
    53  	testTrigger1 := &testtriggersv1.TestTrigger{
    54  		ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
    55  		Spec: testtriggersv1.TestTriggerSpec{
    56  			Resource:         "deployment",
    57  			ResourceSelector: testtriggersv1.TestTriggerSelector{Name: "test-deployment"},
    58  			Event:            "modified",
    59  			ConditionSpec: &testtriggersv1.TestTriggerConditionSpec{
    60  				Timeout: timeout,
    61  				Conditions: []testtriggersv1.TestTriggerCondition{
    62  					{
    63  						Type_:  "Progressing",
    64  						Status: &status,
    65  						Reason: "NewReplicaSetAvailable",
    66  						Ttl:    60,
    67  					},
    68  					{
    69  						Type_:  "Available",
    70  						Status: &status,
    71  					},
    72  				},
    73  			},
    74  			Action:            "run",
    75  			Execution:         "test",
    76  			ConcurrencyPolicy: "allow",
    77  			TestSelector:      testtriggersv1.TestTriggerSelector{Name: "some-test"},
    78  		},
    79  	}
    80  	statusKey1 := newStatusKey(testTrigger1.Namespace, testTrigger1.Name)
    81  	triggerStatus1 := &triggerStatus{testTrigger: testTrigger1}
    82  	s := &Service{
    83  		defaultConditionsCheckBackoff: defaultConditionsCheckBackoff,
    84  		defaultConditionsCheckTimeout: defaultConditionsCheckTimeout,
    85  		triggerExecutor: func(ctx context.Context, e *watcherEvent, trigger *testtriggersv1.TestTrigger) error {
    86  			assert.Equal(t, "testkube", trigger.Namespace)
    87  			assert.Equal(t, "test-trigger-1", trigger.Name)
    88  			return nil
    89  		},
    90  		triggerStatus: map[statusKey]*triggerStatus{statusKey1: triggerStatus1},
    91  		logger:        log.DefaultLogger,
    92  	}
    93  
    94  	err := s.match(context.Background(), e)
    95  	assert.NoError(t, err)
    96  	assert.Equal(t, 1, retry)
    97  }
    98  
    99  func TestService_matchConditionsTimeout(t *testing.T) {
   100  	t.Parallel()
   101  
   102  	e := &watcherEvent{
   103  		resource:  "deployment",
   104  		name:      "test-deployment",
   105  		namespace: "testkube",
   106  		labels:    nil,
   107  		object:    nil,
   108  		eventType: "modified",
   109  		causes:    nil,
   110  		conditionsGetter: func() ([]testtriggersv1.TestTriggerCondition, error) {
   111  			status := testtriggersv1.FALSE_TestTriggerConditionStatuses
   112  			return []testtriggersv1.TestTriggerCondition{
   113  				{
   114  					Type_:  "Progressing",
   115  					Status: &status,
   116  					Reason: "NewReplicaSetAvailable",
   117  					Ttl:    60,
   118  				},
   119  				{
   120  					Type_:  "Available",
   121  					Status: &status,
   122  				},
   123  			}, nil
   124  		},
   125  	}
   126  
   127  	var timeout int32 = 1
   128  	status := testtriggersv1.TRUE_TestTriggerConditionStatuses
   129  	testTrigger1 := &testtriggersv1.TestTrigger{
   130  		ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
   131  		Spec: testtriggersv1.TestTriggerSpec{
   132  			Resource:         "deployment",
   133  			ResourceSelector: testtriggersv1.TestTriggerSelector{Name: "test-deployment"},
   134  			Event:            "modified",
   135  			ConditionSpec: &testtriggersv1.TestTriggerConditionSpec{
   136  				Timeout: timeout,
   137  				Conditions: []testtriggersv1.TestTriggerCondition{
   138  					{
   139  						Type_:  "Progressing",
   140  						Status: &status,
   141  						Reason: "NewReplicaSetAvailable",
   142  						Ttl:    60,
   143  					},
   144  					{
   145  						Type_:  "Available",
   146  						Status: &status,
   147  					},
   148  				},
   149  			},
   150  			Action:            "run",
   151  			Execution:         "test",
   152  			ConcurrencyPolicy: "allow",
   153  			TestSelector:      testtriggersv1.TestTriggerSelector{Name: "some-test"},
   154  		},
   155  	}
   156  	statusKey1 := newStatusKey(testTrigger1.Namespace, testTrigger1.Name)
   157  	triggerStatus1 := &triggerStatus{testTrigger: testTrigger1}
   158  	s := &Service{
   159  		defaultConditionsCheckBackoff: defaultConditionsCheckBackoff,
   160  		defaultConditionsCheckTimeout: defaultConditionsCheckTimeout,
   161  		triggerExecutor: func(ctx context.Context, e *watcherEvent, trigger *testtriggersv1.TestTrigger) error {
   162  			assert.Equal(t, "testkube", trigger.Namespace)
   163  			assert.Equal(t, "test-trigger-1", trigger.Name)
   164  			return nil
   165  		},
   166  		triggerStatus: map[statusKey]*triggerStatus{statusKey1: triggerStatus1},
   167  		logger:        log.DefaultLogger,
   168  	}
   169  
   170  	err := s.match(context.Background(), e)
   171  	assert.ErrorIs(t, err, ErrConditionTimeout)
   172  }
   173  
   174  func TestService_matchProbesMultiple(t *testing.T) {
   175  	t.Parallel()
   176  
   177  	e := &watcherEvent{
   178  		resource:  "deployment",
   179  		name:      "test-deployment",
   180  		namespace: "testkube",
   181  		labels:    nil,
   182  		object:    nil,
   183  		eventType: "modified",
   184  		causes:    nil,
   185  	}
   186  
   187  	srv1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   188  	}))
   189  	defer srv1.Close()
   190  
   191  	url1, err := url.Parse(srv1.URL)
   192  	assert.NoError(t, err)
   193  
   194  	srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   195  	}))
   196  	defer srv2.Close()
   197  
   198  	url2, err := url.Parse(srv2.URL)
   199  	assert.NoError(t, err)
   200  
   201  	testTrigger1 := &testtriggersv1.TestTrigger{
   202  		ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
   203  		Spec: testtriggersv1.TestTriggerSpec{
   204  			Resource:          "deployment",
   205  			ResourceSelector:  testtriggersv1.TestTriggerSelector{Name: "test-deployment"},
   206  			Event:             "modified",
   207  			Action:            "run",
   208  			Execution:         "test",
   209  			ConcurrencyPolicy: "allow",
   210  			TestSelector:      testtriggersv1.TestTriggerSelector{Name: "some-test"},
   211  			ProbeSpec: &testtriggersv1.TestTriggerProbeSpec{
   212  				Probes: []testtriggersv1.TestTriggerProbe{
   213  					{
   214  						Scheme: url1.Scheme,
   215  						Host:   url1.Host,
   216  						Path:   url1.Path,
   217  					},
   218  					{
   219  						Scheme: url2.Scheme,
   220  						Host:   url2.Host,
   221  						Path:   url2.Path,
   222  					},
   223  				},
   224  			},
   225  		},
   226  	}
   227  
   228  	statusKey1 := newStatusKey(testTrigger1.Namespace, testTrigger1.Name)
   229  	triggerStatus1 := &triggerStatus{testTrigger: testTrigger1}
   230  	s := &Service{
   231  		defaultProbesCheckBackoff: defaultProbesCheckBackoff,
   232  		defaultProbesCheckTimeout: defaultProbesCheckTimeout,
   233  		triggerExecutor: func(ctx context.Context, e *watcherEvent, trigger *testtriggersv1.TestTrigger) error {
   234  			assert.Equal(t, "testkube", trigger.Namespace)
   235  			assert.Equal(t, "test-trigger-1", trigger.Name)
   236  			return nil
   237  		},
   238  		triggerStatus: map[statusKey]*triggerStatus{statusKey1: triggerStatus1},
   239  		logger:        log.DefaultLogger,
   240  		httpClient:    http.DefaultClient,
   241  	}
   242  
   243  	err = s.match(context.Background(), e)
   244  	assert.NoError(t, err)
   245  }
   246  
   247  func TestService_matchProbesTimeout(t *testing.T) {
   248  	t.Parallel()
   249  
   250  	e := &watcherEvent{
   251  		resource:  "deployment",
   252  		name:      "test-deployment",
   253  		namespace: "testkube",
   254  		labels:    nil,
   255  		object:    nil,
   256  		eventType: "modified",
   257  		causes:    nil,
   258  	}
   259  
   260  	srv1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   261  	}))
   262  	defer srv1.Close()
   263  
   264  	url1, err := url.Parse(srv1.URL)
   265  	assert.NoError(t, err)
   266  
   267  	testTrigger1 := &testtriggersv1.TestTrigger{
   268  		ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
   269  		Spec: testtriggersv1.TestTriggerSpec{
   270  			Resource:          "deployment",
   271  			ResourceSelector:  testtriggersv1.TestTriggerSelector{Name: "test-deployment"},
   272  			Event:             "modified",
   273  			Action:            "run",
   274  			Execution:         "test",
   275  			ConcurrencyPolicy: "allow",
   276  			TestSelector:      testtriggersv1.TestTriggerSelector{Name: "some-test"},
   277  			ProbeSpec: &testtriggersv1.TestTriggerProbeSpec{
   278  				Timeout: 2,
   279  				Delay:   1,
   280  				Probes: []testtriggersv1.TestTriggerProbe{
   281  					{
   282  						Scheme: url1.Scheme,
   283  						Host:   url1.Host,
   284  						Path:   url1.Path,
   285  					},
   286  					{
   287  						Host: "fakehost",
   288  					},
   289  				},
   290  			},
   291  		},
   292  	}
   293  
   294  	statusKey1 := newStatusKey(testTrigger1.Namespace, testTrigger1.Name)
   295  	triggerStatus1 := &triggerStatus{testTrigger: testTrigger1}
   296  	s := &Service{
   297  		defaultProbesCheckBackoff: defaultProbesCheckBackoff,
   298  		defaultProbesCheckTimeout: defaultProbesCheckTimeout,
   299  		triggerExecutor: func(ctx context.Context, e *watcherEvent, trigger *testtriggersv1.TestTrigger) error {
   300  			assert.Equal(t, "testkube", trigger.Namespace)
   301  			assert.Equal(t, "test-trigger-1", trigger.Name)
   302  			return nil
   303  		},
   304  		triggerStatus: map[statusKey]*triggerStatus{statusKey1: triggerStatus1},
   305  		logger:        log.DefaultLogger,
   306  		httpClient:    http.DefaultClient,
   307  	}
   308  
   309  	err = s.match(context.Background(), e)
   310  	assert.ErrorIs(t, err, ErrProbeTimeout)
   311  
   312  }
   313  
   314  func TestService_match(t *testing.T) {
   315  	t.Parallel()
   316  
   317  	e := &watcherEvent{
   318  		resource:  "deployment",
   319  		name:      "test-deployment",
   320  		namespace: "testkube",
   321  		labels:    nil,
   322  		object:    nil,
   323  		eventType: "modified",
   324  		causes:    nil,
   325  		conditionsGetter: func() ([]testtriggersv1.TestTriggerCondition, error) {
   326  			status := testtriggersv1.TRUE_TestTriggerConditionStatuses
   327  			return []testtriggersv1.TestTriggerCondition{
   328  				{
   329  					Type_:  "Progressing",
   330  					Status: &status,
   331  					Reason: "NewReplicaSetAvailable",
   332  					Ttl:    60,
   333  				},
   334  				{
   335  					Type_:  "Available",
   336  					Status: &status,
   337  				},
   338  			}, nil
   339  		},
   340  	}
   341  
   342  	srv1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   343  	}))
   344  	defer srv1.Close()
   345  
   346  	url1, err := url.Parse(srv1.URL)
   347  	assert.NoError(t, err)
   348  
   349  	srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   350  	}))
   351  	defer srv2.Close()
   352  
   353  	url2, err := url.Parse(srv2.URL)
   354  	assert.NoError(t, err)
   355  
   356  	status := testtriggersv1.TRUE_TestTriggerConditionStatuses
   357  	testTrigger1 := &testtriggersv1.TestTrigger{
   358  		ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
   359  		Spec: testtriggersv1.TestTriggerSpec{
   360  			Resource:         "deployment",
   361  			ResourceSelector: testtriggersv1.TestTriggerSelector{Name: "test-deployment"},
   362  			Event:            "modified",
   363  			ConditionSpec: &testtriggersv1.TestTriggerConditionSpec{
   364  				Conditions: []testtriggersv1.TestTriggerCondition{
   365  					{
   366  						Type_:  "Progressing",
   367  						Status: &status,
   368  						Reason: "NewReplicaSetAvailable",
   369  						Ttl:    60,
   370  					},
   371  					{
   372  						Type_:  "Available",
   373  						Status: &status,
   374  					},
   375  				},
   376  			},
   377  			ProbeSpec: &testtriggersv1.TestTriggerProbeSpec{
   378  				Probes: []testtriggersv1.TestTriggerProbe{
   379  					{
   380  						Scheme: url1.Scheme,
   381  						Host:   url1.Host,
   382  						Path:   url1.Path,
   383  					},
   384  					{
   385  						Scheme: url2.Scheme,
   386  						Host:   url2.Host,
   387  						Path:   url2.Path,
   388  					},
   389  				},
   390  			},
   391  			Action:            "run",
   392  			Execution:         "test",
   393  			ConcurrencyPolicy: "allow",
   394  			TestSelector:      testtriggersv1.TestTriggerSelector{Name: "some-test"},
   395  		},
   396  	}
   397  	statusKey1 := newStatusKey(testTrigger1.Namespace, testTrigger1.Name)
   398  	triggerStatus1 := &triggerStatus{testTrigger: testTrigger1}
   399  	s := &Service{
   400  		defaultConditionsCheckBackoff: defaultConditionsCheckBackoff,
   401  		defaultConditionsCheckTimeout: defaultConditionsCheckTimeout,
   402  		defaultProbesCheckBackoff:     defaultProbesCheckBackoff,
   403  		defaultProbesCheckTimeout:     defaultProbesCheckTimeout,
   404  		triggerExecutor: func(ctx context.Context, e *watcherEvent, trigger *testtriggersv1.TestTrigger) error {
   405  			assert.Equal(t, "testkube", trigger.Namespace)
   406  			assert.Equal(t, "test-trigger-1", trigger.Name)
   407  			return nil
   408  		},
   409  		triggerStatus: map[statusKey]*triggerStatus{statusKey1: triggerStatus1},
   410  		logger:        log.DefaultLogger,
   411  		httpClient:    http.DefaultClient,
   412  	}
   413  
   414  	err = s.match(context.Background(), e)
   415  	assert.NoError(t, err)
   416  }
   417  
   418  func TestService_matchRegex(t *testing.T) {
   419  	t.Parallel()
   420  
   421  	e := &watcherEvent{
   422  		resource:  "deployment",
   423  		name:      "test-deployment",
   424  		namespace: "testkube",
   425  		labels:    nil,
   426  		object:    nil,
   427  		eventType: "modified",
   428  		causes:    nil,
   429  	}
   430  
   431  	srv1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   432  	}))
   433  	defer srv1.Close()
   434  
   435  	testTrigger1 := &testtriggersv1.TestTrigger{
   436  		ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
   437  		Spec: testtriggersv1.TestTriggerSpec{
   438  			Resource:          "deployment",
   439  			ResourceSelector:  testtriggersv1.TestTriggerSelector{NameRegex: "test.*"},
   440  			Event:             "modified",
   441  			Action:            "run",
   442  			Execution:         "test",
   443  			ConcurrencyPolicy: "allow",
   444  			TestSelector:      testtriggersv1.TestTriggerSelector{NameRegex: "some.*"},
   445  		},
   446  	}
   447  	statusKey1 := newStatusKey(testTrigger1.Namespace, testTrigger1.Name)
   448  	triggerStatus1 := &triggerStatus{testTrigger: testTrigger1}
   449  	s := &Service{
   450  		defaultConditionsCheckBackoff: defaultConditionsCheckBackoff,
   451  		defaultConditionsCheckTimeout: defaultConditionsCheckTimeout,
   452  		defaultProbesCheckBackoff:     defaultProbesCheckBackoff,
   453  		defaultProbesCheckTimeout:     defaultProbesCheckTimeout,
   454  		triggerExecutor: func(ctx context.Context, e *watcherEvent, trigger *testtriggersv1.TestTrigger) error {
   455  			assert.Equal(t, "testkube", trigger.Namespace)
   456  			assert.Equal(t, "test-trigger-1", trigger.Name)
   457  			return nil
   458  		},
   459  		triggerStatus: map[statusKey]*triggerStatus{statusKey1: triggerStatus1},
   460  		logger:        log.DefaultLogger,
   461  		httpClient:    http.DefaultClient,
   462  	}
   463  
   464  	err := s.match(context.Background(), e)
   465  	assert.NoError(t, err)
   466  }
   467  
   468  func TestService_noMatch(t *testing.T) {
   469  	t.Parallel()
   470  
   471  	e := &watcherEvent{
   472  		resource:  "deployment",
   473  		name:      "test-deployment",
   474  		namespace: "testkube",
   475  		labels:    nil,
   476  		object:    nil,
   477  		eventType: "modified",
   478  		causes:    nil,
   479  	}
   480  
   481  	testTrigger1 := &testtriggersv1.TestTrigger{
   482  		ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "test-trigger-1"},
   483  		Spec: testtriggersv1.TestTriggerSpec{
   484  			Resource:          "pod",
   485  			ResourceSelector:  testtriggersv1.TestTriggerSelector{Name: "test-pod"},
   486  			Event:             "modified",
   487  			Action:            "run",
   488  			Execution:         "test",
   489  			ConcurrencyPolicy: "allow",
   490  			TestSelector:      testtriggersv1.TestTriggerSelector{Name: "some-test"},
   491  		},
   492  	}
   493  	statusKey1 := newStatusKey(testTrigger1.Namespace, testTrigger1.Name)
   494  	triggerStatus1 := &triggerStatus{testTrigger: testTrigger1}
   495  	testExecutorF := func(ctx context.Context, e *watcherEvent, trigger *testtriggersv1.TestTrigger) error {
   496  		assert.Fail(t, "should not match event")
   497  		return nil
   498  	}
   499  	s := &Service{
   500  		triggerExecutor: testExecutorF,
   501  		triggerStatus:   map[statusKey]*triggerStatus{statusKey1: triggerStatus1},
   502  		logger:          log.DefaultLogger,
   503  	}
   504  
   505  	err := s.match(context.Background(), e)
   506  	assert.NoError(t, err)
   507  }