github.com/argoproj/argo-events@v1.9.1/sensors/triggers/params_test.go (about)

     1  /*
     2  Copyright 2018 BlackRock, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package triggers
    18  
    19  import (
    20  	"encoding/json"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/ghodss/yaml"
    25  	"github.com/stretchr/testify/assert"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  
    29  	"github.com/argoproj/argo-events/common"
    30  	apicommon "github.com/argoproj/argo-events/pkg/apis/common"
    31  	"github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1"
    32  )
    33  
    34  var sensorObj = &v1alpha1.Sensor{
    35  	ObjectMeta: metav1.ObjectMeta{
    36  		Name:      "fake-sensor",
    37  		Namespace: "fake",
    38  	},
    39  	Spec: v1alpha1.SensorSpec{
    40  		Triggers: []v1alpha1.Trigger{
    41  			{
    42  				Template: &v1alpha1.TriggerTemplate{
    43  					Name: "fake-trigger",
    44  					K8s:  &v1alpha1.StandardK8STrigger{},
    45  				},
    46  			},
    47  		},
    48  	},
    49  }
    50  
    51  func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
    52  	return &unstructured.Unstructured{
    53  		Object: map[string]interface{}{
    54  			"apiVersion": apiVersion,
    55  			"kind":       kind,
    56  			"metadata": map[string]interface{}{
    57  				"namespace": namespace,
    58  				"name":      name,
    59  				"labels": map[string]interface{}{
    60  					"name": name,
    61  				},
    62  			},
    63  		},
    64  	}
    65  }
    66  
    67  type Details struct {
    68  	Street string `json:"street"`
    69  	City   string `json:"city"`
    70  	Pin    string `json:"pin"`
    71  }
    72  
    73  type Payload struct {
    74  	FirstName        string  `json:"firstName"`
    75  	LastName         string  `json:"lastName"`
    76  	Age              int     `json:"age"`
    77  	IsActive         bool    `json:"isActive"`
    78  	TypelessAge      string  `json:"typelessAge"`
    79  	TypelessIsActive string  `json:"typelessIsActive"`
    80  	Details          Details `json:"details"`
    81  }
    82  
    83  func TestConstructPayload(t *testing.T) {
    84  	testEvents := map[string]*v1alpha1.Event{
    85  		"fake-dependency": {
    86  			Context: &v1alpha1.EventContext{
    87  				ID:              "1",
    88  				Type:            "webhook",
    89  				Source:          "webhook-gateway",
    90  				DataContentType: common.MediaTypeJSON,
    91  				Subject:         "example-1",
    92  			},
    93  			Data: []byte("{\"firstName\": \"fake\"}"),
    94  		},
    95  		"another-fake-dependency": {
    96  			Context: &v1alpha1.EventContext{
    97  				ID:              "2",
    98  				Type:            "calendar",
    99  				Source:          "calendar-gateway",
   100  				DataContentType: common.MediaTypeJSON,
   101  				Subject:         "example-1",
   102  			},
   103  			Data: []byte("{\"lastName\": \"foo\"}"),
   104  		},
   105  		"use-event-data-type": {
   106  			Context: &v1alpha1.EventContext{
   107  				ID:              "3",
   108  				Type:            "calendar",
   109  				Source:          "calendar-gateway",
   110  				DataContentType: common.MediaTypeJSON,
   111  				Subject:         "example-1",
   112  			},
   113  			Data: []byte("{\"age\": 100, \"isActive\": false, \"countries\": [\"ca\", \"us\", \"mx\"]}"),
   114  		},
   115  	}
   116  
   117  	defaultFirstName := "faker"
   118  	defaultLastName := "bar"
   119  
   120  	parameters := []v1alpha1.TriggerParameter{
   121  		{
   122  			Src: &v1alpha1.TriggerParameterSource{
   123  				DependencyName: "fake-dependency",
   124  				DataKey:        "firstName",
   125  				Value:          &defaultFirstName,
   126  			},
   127  			Dest: "firstName",
   128  		},
   129  		{
   130  			Src: &v1alpha1.TriggerParameterSource{
   131  				DependencyName: "another-fake-dependency",
   132  				DataKey:        "lastName",
   133  				Value:          &defaultLastName,
   134  			},
   135  			Dest: "lastName",
   136  		},
   137  		{
   138  			Src: &v1alpha1.TriggerParameterSource{
   139  				DependencyName: "use-event-data-type",
   140  				DataKey:        "age",
   141  				UseRawData:     true,
   142  			},
   143  			Dest: "age",
   144  		},
   145  		{
   146  			Src: &v1alpha1.TriggerParameterSource{
   147  				DependencyName: "use-event-data-type",
   148  				DataKey:        "isActive",
   149  				UseRawData:     true,
   150  			},
   151  			Dest: "isActive",
   152  		},
   153  		{
   154  			Src: &v1alpha1.TriggerParameterSource{
   155  				DependencyName: "use-event-data-type",
   156  				DataKey:        "age",
   157  			},
   158  			Dest: "typelessAge",
   159  		},
   160  		{
   161  			Src: &v1alpha1.TriggerParameterSource{
   162  				DependencyName: "use-event-data-type",
   163  				DataKey:        "isActive",
   164  			},
   165  			Dest: "typelessIsActive",
   166  		},
   167  	}
   168  
   169  	payloadBytes, err := ConstructPayload(testEvents, parameters)
   170  	assert.Nil(t, err)
   171  	assert.NotNil(t, payloadBytes)
   172  
   173  	var p *Payload
   174  	err = json.Unmarshal(payloadBytes, &p)
   175  	assert.Nil(t, err)
   176  	assert.Equal(t, "fake", p.FirstName)
   177  	assert.Equal(t, "foo", p.LastName)
   178  	assert.Equal(t, 100, p.Age)
   179  	assert.Equal(t, false, p.IsActive)
   180  	assert.Equal(t, "100", p.TypelessAge)
   181  	assert.Equal(t, "false", p.TypelessIsActive)
   182  
   183  	parameters[0].Src.DataKey = "unknown"
   184  	parameters[1].Src.DataKey = "unknown"
   185  
   186  	payloadBytes, err = ConstructPayload(testEvents, parameters)
   187  	assert.Nil(t, err)
   188  	assert.NotNil(t, payloadBytes)
   189  
   190  	err = json.Unmarshal(payloadBytes, &p)
   191  	assert.Nil(t, err)
   192  	assert.Equal(t, "faker", p.FirstName)
   193  	assert.Equal(t, "bar", p.LastName)
   194  }
   195  
   196  func TestResolveParamValue(t *testing.T) {
   197  	event := &v1alpha1.Event{
   198  		Context: &v1alpha1.EventContext{
   199  			DataContentType: common.MediaTypeJSON,
   200  			Subject:         "example-1",
   201  			Source:          "webhook-gateway",
   202  			Type:            "webhook",
   203  			ID:              "1",
   204  			Time:            metav1.Time{Time: time.Now().UTC()},
   205  		},
   206  		Data: []byte("{\"name\": {\"first\": \"fake\", \"last\": \"user\"}, \"reviews\": 8, \"rating\": 4.5, \"isActive\" : true, \"isVerified\" : false, \"countries\": [\"ca\", \"us\", \"mx\"]}"),
   207  	}
   208  	eventBody, err := json.Marshal(event)
   209  	assert.Nil(t, err)
   210  
   211  	events := map[string]*v1alpha1.Event{
   212  		"fake-dependency": event,
   213  	}
   214  
   215  	defaultValue := "hello"
   216  
   217  	tests := []struct {
   218  		name   string
   219  		source *v1alpha1.TriggerParameterSource
   220  		result string
   221  	}{
   222  		{
   223  			name: "get first name",
   224  			source: &v1alpha1.TriggerParameterSource{
   225  				DependencyName: "fake-dependency",
   226  				DataKey:        "name.first",
   227  			},
   228  			result: "fake",
   229  		},
   230  		{
   231  			name: "get the event subject",
   232  			source: &v1alpha1.TriggerParameterSource{
   233  				DependencyName: "fake-dependency",
   234  				ContextKey:     "subject",
   235  			},
   236  			result: "example-1",
   237  		},
   238  		{
   239  			name: "get the entire payload",
   240  			source: &v1alpha1.TriggerParameterSource{
   241  				DependencyName: "fake-dependency",
   242  			},
   243  			result: string(eventBody),
   244  		},
   245  		{
   246  			name: "get the default value",
   247  			source: &v1alpha1.TriggerParameterSource{
   248  				DependencyName: "fake-dependency",
   249  				Value:          &defaultValue,
   250  			},
   251  			result: defaultValue,
   252  		},
   253  		{
   254  			name: "data key has preference over context key",
   255  			source: &v1alpha1.TriggerParameterSource{
   256  				DependencyName: "fake-dependency",
   257  				ContextKey:     "subject",
   258  				DataKey:        "name.first",
   259  			},
   260  			result: "fake",
   261  		},
   262  		{
   263  			name: "get first name with template",
   264  			source: &v1alpha1.TriggerParameterSource{
   265  				DependencyName: "fake-dependency",
   266  				DataTemplate:   "{{ .Input.name.first }}",
   267  			},
   268  			result: "fake",
   269  		},
   270  		{
   271  			name: "get capitalized first name with template",
   272  			source: &v1alpha1.TriggerParameterSource{
   273  				DependencyName: "fake-dependency",
   274  				DataTemplate:   "{{ upper .Input.name.first }}",
   275  			},
   276  			result: "FAKE",
   277  		},
   278  		{
   279  			name: "get subject with template",
   280  			source: &v1alpha1.TriggerParameterSource{
   281  				DependencyName:  "fake-dependency",
   282  				ContextTemplate: "{{ .Input.subject }}",
   283  			},
   284  			result: "example-1",
   285  		},
   286  		{
   287  			name: "get formatted subject with template",
   288  			source: &v1alpha1.TriggerParameterSource{
   289  				DependencyName:  "fake-dependency",
   290  				ContextTemplate: `{{ .Input.subject | replace "-" "_" }}`,
   291  			},
   292  			result: "example_1",
   293  		},
   294  		{
   295  			name: "data template has preference over context template",
   296  			source: &v1alpha1.TriggerParameterSource{
   297  				DependencyName:  "fake-dependency",
   298  				ContextTemplate: "{{ .Input.subject }}",
   299  				DataTemplate:    "{{ .Input.name.first }}",
   300  			},
   301  			result: "fake",
   302  		},
   303  		{
   304  			name: "data template fails over to data key",
   305  			source: &v1alpha1.TriggerParameterSource{
   306  				DependencyName: "fake-dependency",
   307  				DataTemplate:   "{{ .Input.name.non_exist }}",
   308  				DataKey:        "name.first",
   309  			},
   310  			result: "fake",
   311  		},
   312  		{
   313  			name: "invalid template fails over to data key",
   314  			source: &v1alpha1.TriggerParameterSource{
   315  				DependencyName: "fake-dependency",
   316  				DataTemplate:   "{{ no }}",
   317  				DataKey:        "name.first",
   318  			},
   319  			result: "fake",
   320  		},
   321  		{
   322  			name: "UseRawData set to true - string",
   323  			source: &v1alpha1.TriggerParameterSource{
   324  				DependencyName: "fake-dependency",
   325  				DataKey:        "name.first",
   326  				UseRawData:     true,
   327  			},
   328  			result: "fake",
   329  		},
   330  		{
   331  			name: "UseRawData set to true - json",
   332  			source: &v1alpha1.TriggerParameterSource{
   333  				DependencyName: "fake-dependency",
   334  				DataKey:        "name",
   335  				UseRawData:     true,
   336  			},
   337  			result: "{\"first\": \"fake\", \"last\": \"user\"}",
   338  		},
   339  		{
   340  			name: "UseRawData set to true - list",
   341  			source: &v1alpha1.TriggerParameterSource{
   342  				DependencyName: "fake-dependency",
   343  				DataKey:        "countries",
   344  				UseRawData:     true,
   345  			},
   346  			result: "[\"ca\", \"us\", \"mx\"]",
   347  		},
   348  	}
   349  
   350  	for _, test := range tests {
   351  		t.Run(test.name, func(t *testing.T) {
   352  			result, _, err := ResolveParamValue(test.source, events)
   353  			assert.Nil(t, err)
   354  			assert.Equal(t, test.result, *result)
   355  		})
   356  	}
   357  }
   358  
   359  func TestRenderDataAsJSON(t *testing.T) {
   360  	event := &v1alpha1.Event{
   361  		Context: &v1alpha1.EventContext{
   362  			DataContentType: common.MediaTypeJSON,
   363  			Subject:         "example-1",
   364  			Source:          "webhook-gateway",
   365  			Type:            "webhook",
   366  			ID:              "1",
   367  			Time:            metav1.Time{Time: time.Now().UTC()},
   368  		},
   369  		Data: []byte("{\"name\": {\"first\": \"fake\", \"last\": \"user\"} }"),
   370  	}
   371  	body, err := renderEventDataAsJSON(event)
   372  	assert.Nil(t, err)
   373  	assert.Equal(t, string(body), "{\"name\": {\"first\": \"fake\", \"last\": \"user\"} }")
   374  
   375  	testYaml := struct {
   376  		Name string `json:"name"`
   377  		Age  int    `json:"age"`
   378  	}{
   379  		Name: "test",
   380  		Age:  20,
   381  	}
   382  
   383  	yamlBody, err := yaml.Marshal(&testYaml)
   384  	assert.Nil(t, err)
   385  	event.Data = yamlBody
   386  	event.Context.DataContentType = common.MediaTypeYAML
   387  	assert.Nil(t, err)
   388  	body, err = renderEventDataAsJSON(event)
   389  	assert.Nil(t, err)
   390  	assert.Equal(t, string(body), "{\"age\":20,\"name\":\"test\"}")
   391  }
   392  
   393  func TestApplyParams(t *testing.T) {
   394  	event := &v1alpha1.Event{
   395  		Context: &v1alpha1.EventContext{
   396  			DataContentType: common.MediaTypeJSON,
   397  			Subject:         "example-1",
   398  			Source:          "webhook-gateway",
   399  			Type:            "webhook",
   400  			ID:              "1",
   401  			Time:            metav1.Time{Time: time.Now().UTC()},
   402  		},
   403  		Data: []byte("{\"name\": {\"first\": \"fake\", \"last\": \"user\"}, \"age\": 100, \"countries\": [\"ca\", \"us\", \"mx\"] }"),
   404  	}
   405  
   406  	events := map[string]*v1alpha1.Event{
   407  		"fake-dependency": event,
   408  	}
   409  
   410  	tests := []struct {
   411  		name    string
   412  		params  []v1alpha1.TriggerParameter
   413  		jsonObj []byte
   414  		result  []byte
   415  	}{
   416  		{
   417  			name: "normal apply parameters operation",
   418  			params: []v1alpha1.TriggerParameter{
   419  				{
   420  					Src: &v1alpha1.TriggerParameterSource{
   421  						DependencyName: "fake-dependency",
   422  						DataKey:        "name.first",
   423  					},
   424  					Dest:      "name",
   425  					Operation: v1alpha1.TriggerParameterOpNone,
   426  				},
   427  			},
   428  			jsonObj: []byte("{\"name\": \"faker\"}"),
   429  			result:  []byte("{\"name\": \"fake\"}"),
   430  		},
   431  		{
   432  			name: "apply parameters with prepend operation",
   433  			params: []v1alpha1.TriggerParameter{
   434  				{
   435  					Src: &v1alpha1.TriggerParameterSource{
   436  						DependencyName: "fake-dependency",
   437  						DataKey:        "name.first",
   438  					},
   439  					Dest:      "name",
   440  					Operation: v1alpha1.TriggerParameterOpPrepend,
   441  				},
   442  			},
   443  			jsonObj: []byte("{\"name\": \"faker\"}"),
   444  			result:  []byte("{\"name\": \"fakefaker\"}"),
   445  		},
   446  		{
   447  			name: "apply parameters with append operation",
   448  			params: []v1alpha1.TriggerParameter{
   449  				{
   450  					Src: &v1alpha1.TriggerParameterSource{
   451  						DependencyName: "fake-dependency",
   452  						DataKey:        "name.first",
   453  					},
   454  					Dest:      "name",
   455  					Operation: v1alpha1.TriggerParameterOpAppend,
   456  				},
   457  			},
   458  			jsonObj: []byte("{\"name\": \"faker\"}"),
   459  			result:  []byte("{\"name\": \"fakerfake\"}"),
   460  		},
   461  		{
   462  			name: "apply parameters with overwrite operation",
   463  			params: []v1alpha1.TriggerParameter{
   464  				{
   465  					Src: &v1alpha1.TriggerParameterSource{
   466  						DependencyName: "fake-dependency",
   467  						DataKey:        "name.first",
   468  					},
   469  					Dest:      "name",
   470  					Operation: v1alpha1.TriggerParameterOpOverwrite,
   471  				},
   472  			},
   473  			jsonObj: []byte("{\"name\": \"faker\"}"),
   474  			result:  []byte("{\"name\": \"fake\"}"),
   475  		},
   476  		{
   477  			name: "apply block parameters with overwrite operation - useRawDataValue false",
   478  			params: []v1alpha1.TriggerParameter{
   479  				{
   480  					Src: &v1alpha1.TriggerParameterSource{
   481  						DependencyName: "fake-dependency",
   482  						DataKey:        "name",
   483  					},
   484  					Dest:      "name",
   485  					Operation: v1alpha1.TriggerParameterOpOverwrite,
   486  				},
   487  			},
   488  			jsonObj: []byte("{\"name\": \"faker\"}"),
   489  			result:  []byte("{\"name\": \"{\\\"first\\\": \\\"fake\\\", \\\"last\\\": \\\"user\\\"}\"}"),
   490  		},
   491  		{
   492  			name: "apply block parameters with overwrite operation - useRawDataValue true",
   493  			params: []v1alpha1.TriggerParameter{
   494  				{
   495  					Src: &v1alpha1.TriggerParameterSource{
   496  						DependencyName: "fake-dependency",
   497  						DataKey:        "name",
   498  						UseRawData:     true,
   499  					},
   500  					Dest:      "name",
   501  					Operation: v1alpha1.TriggerParameterOpOverwrite,
   502  				},
   503  			},
   504  			jsonObj: []byte("{\"name\": \"faker\"}"),
   505  			result:  []byte("{\"name\": {\"first\": \"fake\", \"last\": \"user\"}}"),
   506  		},
   507  		{
   508  			name: "Use raw data types",
   509  			params: []v1alpha1.TriggerParameter{
   510  				{
   511  					Src: &v1alpha1.TriggerParameterSource{
   512  						DependencyName: "fake-dependency",
   513  						DataKey:        "age",
   514  						UseRawData:     true,
   515  					},
   516  					Dest:      "age",
   517  					Operation: v1alpha1.TriggerParameterOpOverwrite,
   518  				},
   519  				{
   520  					Src: &v1alpha1.TriggerParameterSource{
   521  						DependencyName: "fake-dependency",
   522  						DataKey:        "age",
   523  						UseRawData:     true,
   524  					},
   525  					Dest:      "ageWithYears",
   526  					Operation: v1alpha1.TriggerParameterOpAppend,
   527  				},
   528  				{
   529  					Src: &v1alpha1.TriggerParameterSource{
   530  						DependencyName: "fake-dependency",
   531  						DataKey:        "countries",
   532  						UseRawData:     true,
   533  					},
   534  					Dest:      "countries",
   535  					Operation: v1alpha1.TriggerParameterOpAppend,
   536  				},
   537  			},
   538  			jsonObj: []byte("{\"age\": \"this-gets-over-written\", \"ageWithYears\": \"Years: \"}"),
   539  			result:  []byte("{\"age\": 100, \"ageWithYears\": \"Years: 100\",\"countries\":[\"ca\", \"us\", \"mx\"]}"),
   540  		},
   541  	}
   542  
   543  	for _, test := range tests {
   544  		t.Run(test.name, func(t *testing.T) {
   545  			result, err := ApplyParams(test.jsonObj, test.params, events)
   546  			assert.Nil(t, err)
   547  			assert.Equal(t, string(test.result), string(result))
   548  		})
   549  	}
   550  }
   551  
   552  func TestApplyResourceParameters(t *testing.T) {
   553  	obj := sensorObj.DeepCopy()
   554  	deployment := newUnstructured("apps/v1", "Deployment", "fake-deployment", "fake")
   555  
   556  	event := &v1alpha1.Event{
   557  		Context: &v1alpha1.EventContext{
   558  			DataContentType: common.MediaTypeJSON,
   559  			Subject:         "example-1",
   560  			Source:          "webhook-gateway",
   561  			Type:            "webhook",
   562  			ID:              "1",
   563  			Time:            metav1.Time{Time: time.Now().UTC()},
   564  		},
   565  		Data: []byte("{\"name\": {\"first\": \"test-deployment\"} }"),
   566  	}
   567  
   568  	testEvents := map[string]*v1alpha1.Event{
   569  		"fake-dependency": event,
   570  	}
   571  
   572  	artifact := apicommon.NewResource(deployment)
   573  	obj.Spec.Triggers[0].Template.K8s.Source = &v1alpha1.ArtifactLocation{
   574  		Resource: &artifact,
   575  	}
   576  	obj.Spec.Triggers[0].Template.K8s.Parameters = []v1alpha1.TriggerParameter{
   577  		{
   578  			Src: &v1alpha1.TriggerParameterSource{
   579  				DependencyName: "fake-dependency",
   580  				DataKey:        "name.first",
   581  			},
   582  			Operation: v1alpha1.TriggerParameterOpNone,
   583  			Dest:      "metadata.name",
   584  		},
   585  	}
   586  
   587  	err := ApplyResourceParameters(testEvents, obj.Spec.Triggers[0].Template.K8s.Parameters, deployment)
   588  	assert.Nil(t, err)
   589  	assert.Equal(t, deployment.GetName(), "test-deployment")
   590  }
   591  
   592  func TestApplyTemplateParameters(t *testing.T) {
   593  	obj := sensorObj.DeepCopy()
   594  	event := &v1alpha1.Event{
   595  		Context: &v1alpha1.EventContext{
   596  			DataContentType: common.MediaTypeJSON,
   597  			Subject:         "example-1",
   598  			Source:          "webhook-gateway",
   599  			Type:            "webhook",
   600  			ID:              "1",
   601  			Time:            metav1.Time{Time: time.Now().UTC()},
   602  		},
   603  		Data: []byte("{\"group\": \"fake\" }"),
   604  	}
   605  	testEvents := map[string]*v1alpha1.Event{
   606  		"fake-dependency": event,
   607  	}
   608  
   609  	obj.Spec.Triggers[0].Parameters = []v1alpha1.TriggerParameter{
   610  		{
   611  			Src: &v1alpha1.TriggerParameterSource{
   612  				DependencyName: "fake-dependency",
   613  				DataKey:        "group",
   614  			},
   615  			Operation: v1alpha1.TriggerParameterOpOverwrite,
   616  			Dest:      "k8s.group",
   617  		},
   618  	}
   619  	err := ApplyTemplateParameters(testEvents, &obj.Spec.Triggers[0])
   620  	assert.Nil(t, err)
   621  }