github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/aagent/watchers/expressionwatcher/expression_test.go (about)

     1  // Copyright (c) 2024, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package expressionwatcher
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/choria-io/go-choria/aagent/model"
    13  	"github.com/choria-io/go-choria/aagent/watchers/event"
    14  	"github.com/golang/mock/gomock"
    15  	. "github.com/onsi/ginkgo/v2"
    16  	. "github.com/onsi/gomega"
    17  )
    18  
    19  func TestMachine(t *testing.T) {
    20  	RegisterFailHandler(Fail)
    21  	RunSpecs(t, "AAgent/Watchers/ExpressionsWatcher")
    22  }
    23  
    24  var _ = Describe("AAgent/Watchers/ExpressionsWatcher", func() {
    25  	var (
    26  		w       *Watcher
    27  		machine *model.MockMachine
    28  		mockctl *gomock.Controller
    29  		td      string
    30  		err     error
    31  	)
    32  
    33  	BeforeEach(func() {
    34  		td = GinkgoT().TempDir()
    35  
    36  		mockctl = gomock.NewController(GinkgoT())
    37  
    38  		machine = model.NewMockMachine(mockctl)
    39  		machine.EXPECT().Directory().Return(td).AnyTimes()
    40  		machine.EXPECT().SignerKey().Return("").AnyTimes()
    41  
    42  		var wi any
    43  		wi, err = New(machine, "ginkgo_machine", nil, "FAIL_EVENT", "SUCCESS_EVENT", "1m", time.Hour, map[string]any{
    44  			"success_when": "true",
    45  		})
    46  		Expect(err).ToNot(HaveOccurred())
    47  		w = wi.(*Watcher)
    48  	})
    49  
    50  	AfterEach(func() {
    51  		mockctl.Finish()
    52  	})
    53  
    54  	Describe("handleCheck", func() {
    55  		var now time.Time
    56  
    57  		BeforeEach(func() {
    58  			now = time.Now()
    59  			machine.EXPECT().Identity().Return("ginkgo.example.net").AnyTimes()
    60  			machine.EXPECT().InstanceID().Return("123").AnyTimes()
    61  			machine.EXPECT().Version().Return("1.0.0").AnyTimes()
    62  			machine.EXPECT().TimeStampSeconds().Return(now.Unix()).AnyTimes()
    63  			machine.EXPECT().Name().Return("ginkgo").AnyTimes()
    64  		})
    65  
    66  		It("Should handle SuccessWhen", func() {
    67  			w.previous = Skipped
    68  
    69  			machine.EXPECT().Infof(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
    70  			machine.EXPECT().Infof(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
    71  			machine.EXPECT().NotifyWatcherState("ginkgo_machine", gomock.Eq(&StateNotification{
    72  				Event:           event.New(w.name, wtype, version, w.machine),
    73  				PreviousOutcome: stateNames[SuccessWhen],
    74  			})).Times(2)
    75  
    76  			// noce only since second time would be a flip-flop
    77  			machine.EXPECT().Transition("SUCCESS_EVENT").Times(1)
    78  
    79  			err := w.handleCheck(SuccessWhen, nil)
    80  			Expect(err).ToNot(HaveOccurred())
    81  			Expect(w.previous).To(Equal(SuccessWhen))
    82  
    83  			err = w.handleCheck(SuccessWhen, nil)
    84  			Expect(err).ToNot(HaveOccurred())
    85  			Expect(w.previous).To(Equal(SuccessWhen))
    86  		})
    87  
    88  		It("Should handle FailWhen", func() {
    89  			w.previous = Skipped
    90  
    91  			machine.EXPECT().Infof(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
    92  			machine.EXPECT().Infof(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
    93  			machine.EXPECT().NotifyWatcherState("ginkgo_machine", gomock.Eq(&StateNotification{
    94  				Event:           event.New(w.name, wtype, version, w.machine),
    95  				PreviousOutcome: stateNames[FailWhen],
    96  			})).Times(2)
    97  
    98  			// noce only since second time would be a flip-flop
    99  			machine.EXPECT().Transition("FAIL_EVENT").Times(1)
   100  
   101  			err := w.handleCheck(FailWhen, nil)
   102  			Expect(err).ToNot(HaveOccurred())
   103  			Expect(w.previous).To(Equal(FailWhen))
   104  
   105  			err = w.handleCheck(FailWhen, nil)
   106  			Expect(err).ToNot(HaveOccurred())
   107  			Expect(w.previous).To(Equal(FailWhen))
   108  		})
   109  
   110  		It("Should handle Error", func() {
   111  			machine.EXPECT().Errorf("ginkgo_machine", gomock.Any(), gomock.Any()).Times(1)
   112  			machine.EXPECT().NotifyWatcherState("ginkgo_machine", gomock.Eq(&StateNotification{
   113  				Event:           event.New(w.name, wtype, version, w.machine),
   114  				PreviousOutcome: stateNames[Error],
   115  			})).Times(1)
   116  
   117  			err := w.handleCheck(Error, fmt.Errorf("simulated"))
   118  			Expect(err).ToNot(HaveOccurred())
   119  		})
   120  	})
   121  
   122  	Describe("watch", func() {
   123  		BeforeEach(func() {
   124  			machine.EXPECT().Data().Return(map[string]any{"test": 1}).AnyTimes()
   125  			machine.EXPECT().Facts().Return([]byte(`{"fqdn":"ginkgo.example.net"}`)).AnyTimes()
   126  			machine.EXPECT().Identity().Return("ginkgo.example.net").AnyTimes()
   127  
   128  			w.properties.FailWhen = ""
   129  			w.properties.SuccessWhen = ""
   130  		})
   131  
   132  		It("Should handle success_when expressions", func() {
   133  			machine.EXPECT().Debugf(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   134  			w.properties.SuccessWhen = "data.test == 1"
   135  			state, err := w.watch()
   136  			Expect(err).ToNot(HaveOccurred())
   137  			Expect(state).To(Equal(SuccessWhen))
   138  
   139  			w.properties.SuccessWhen = "data.test == 2"
   140  			state, err = w.watch()
   141  			Expect(err).ToNot(HaveOccurred())
   142  			Expect(state).To(Equal(NoMatch))
   143  
   144  			w.properties.SuccessWhen = "1"
   145  			state, err = w.watch()
   146  			Expect(err).To(MatchError("expected bool, but got int"))
   147  			Expect(state).To(Equal(Error))
   148  		})
   149  
   150  		It("Should handle fail_when expressions", func() {
   151  			machine.EXPECT().Debugf(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   152  			w.properties.FailWhen = "data.test == 1"
   153  			state, err := w.watch()
   154  			Expect(err).ToNot(HaveOccurred())
   155  			Expect(state).To(Equal(FailWhen))
   156  
   157  			w.properties.FailWhen = "data.test == 2"
   158  			state, err = w.watch()
   159  			Expect(err).ToNot(HaveOccurred())
   160  			Expect(state).To(Equal(NoMatch))
   161  
   162  			w.properties.FailWhen = "1"
   163  			state, err = w.watch()
   164  			Expect(err).To(MatchError("expected bool, but got int"))
   165  			Expect(state).To(Equal(Error))
   166  		})
   167  	})
   168  
   169  	Describe("setProperties", func() {
   170  		It("Should validate the interval", func() {
   171  			w.interval = time.Millisecond
   172  			Expect(w.setProperties(nil)).To(MatchError("interval should be more than 1 second: 1ms"))
   173  		})
   174  
   175  		It("Should require one expressions", func() {
   176  			w.properties.FailWhen = ""
   177  			w.properties.SuccessWhen = ""
   178  
   179  			Expect(w.setProperties(nil)).To(MatchError("success_when or fail_when is required"))
   180  
   181  			w.properties.FailWhen = "true"
   182  			Expect(w.setProperties(nil)).ToNot(HaveOccurred())
   183  
   184  			w.properties.FailWhen = ""
   185  			w.properties.SuccessWhen = "true"
   186  			Expect(w.setProperties(nil)).ToNot(HaveOccurred())
   187  		})
   188  	})
   189  })