open-cluster-management.io/governance-policy-propagator@v0.13.0/test/e2e/case18_compliance_api_test.go (about)

     1  // Copyright Contributors to the Open Cluster Management project
     2  
     3  package e2e
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"crypto/tls"
     9  	"database/sql"
    10  	"encoding/csv"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"net/http"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/lib/pq"
    20  	. "github.com/onsi/ginkgo/v2"
    21  	. "github.com/onsi/gomega"
    22  	v1 "k8s.io/api/core/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/client-go/kubernetes"
    25  	"k8s.io/client-go/rest"
    26  	ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
    27  
    28  	"open-cluster-management.io/governance-policy-propagator/controllers/complianceeventsapi"
    29  	"open-cluster-management.io/governance-policy-propagator/test/utils"
    30  )
    31  
    32  const (
    33  	eventsEndpoint = "http://localhost:8385/api/v1/compliance-events"
    34  	csvEndpoint    = "http://localhost:8385/api/v1/reports/compliance-events"
    35  )
    36  
    37  var httpClient = http.Client{
    38  	Timeout: 30 * time.Second,
    39  	Transport: &http.Transport{
    40  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    41  	},
    42  }
    43  
    44  var (
    45  	wrongSAToken  string
    46  	subsetSAToken string
    47  )
    48  
    49  const (
    50  	wrongSAYaml  = "../resources/case18_compliance_api_test/wrong_service_account.yaml"
    51  	subsetSAYaml = "../resources/case18_compliance_api_test/subset_service_account.yaml"
    52  )
    53  
    54  func getTableNames(db *sql.DB) ([]string, error) {
    55  	tableNameRows, err := db.Query("SELECT tablename FROM pg_tables WHERE schemaname = current_schema()")
    56  	if err != nil {
    57  		return nil, err
    58  	} else if tableNameRows.Err() != nil {
    59  		return nil, err
    60  	}
    61  
    62  	defer tableNameRows.Close()
    63  
    64  	tableNames := []string{}
    65  
    66  	for tableNameRows.Next() {
    67  		var tableName string
    68  
    69  		err := tableNameRows.Scan(&tableName)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  
    74  		tableNames = append(tableNames, tableName)
    75  	}
    76  
    77  	return tableNames, nil
    78  }
    79  
    80  // Note: These tests require a running Postgres server running in the Kind cluster from the "postgres" Make target.
    81  var _ = Describe("Test the compliance events API", Label("compliance-events-api"), Serial, Ordered, func() {
    82  	var k8sConfig *rest.Config
    83  	var k8sClient *kubernetes.Clientset
    84  	var db *sql.DB
    85  
    86  	BeforeAll(func(ctx context.Context) {
    87  		var err error
    88  
    89  		k8sConfig, err = LoadConfig("", "", "")
    90  		Expect(err).ToNot(HaveOccurred())
    91  
    92  		Expect(clientToken).ToNot(BeEmpty(), "Ensure you use the service account kubeconfig (kubeconfig_hub)")
    93  
    94  		k8sClient, err = kubernetes.NewForConfig(k8sConfig)
    95  		Expect(err).ToNot(HaveOccurred())
    96  
    97  		connectionURL := "postgresql://grc:grc@localhost:5432/ocm-compliance-history?sslmode=disable"
    98  		db, err = sql.Open("postgres", connectionURL)
    99  		DeferCleanup(func() {
   100  			if db == nil {
   101  				return
   102  			}
   103  
   104  			Expect(db.Close()).To(Succeed())
   105  		})
   106  
   107  		Expect(err).ToNot(HaveOccurred())
   108  
   109  		Expect(db.PingContext(ctx)).To(Succeed())
   110  
   111  		// Drop all tables to start fresh
   112  		tableNameRows, err := db.Query("SELECT tablename FROM pg_tables WHERE schemaname = current_schema()")
   113  		Expect(err).ToNot(HaveOccurred())
   114  
   115  		defer tableNameRows.Close()
   116  
   117  		tableNames, err := getTableNames(db)
   118  		Expect(err).ToNot(HaveOccurred())
   119  
   120  		for _, tableName := range tableNames {
   121  			_, err := db.ExecContext(ctx, "DROP TABLE IF EXISTS "+tableName+" CASCADE")
   122  			Expect(err).ToNot(HaveOccurred())
   123  		}
   124  
   125  		ctrllog.SetLogger(GinkgoLogr)
   126  
   127  		complianceServerCtx, err := complianceeventsapi.NewComplianceServerCtx(connectionURL, "unknown")
   128  		Expect(err).ToNot(HaveOccurred())
   129  
   130  		err = complianceServerCtx.MigrateDB(ctx, k8sClient, "open-cluster-management")
   131  		Expect(err).ToNot(HaveOccurred())
   132  
   133  		complianceAPI := complianceeventsapi.NewComplianceAPIServer("localhost:8385", k8sConfig, nil)
   134  
   135  		httpCtx, httpCtxCancel := context.WithCancel(context.Background())
   136  
   137  		go func() {
   138  			defer GinkgoRecover()
   139  
   140  			err = complianceAPI.Start(httpCtx, complianceServerCtx)
   141  			Expect(err).ToNot(HaveOccurred())
   142  		}()
   143  
   144  		DeferCleanup(func() {
   145  			httpCtxCancel()
   146  		})
   147  
   148  		Expect(err).ToNot(HaveOccurred())
   149  
   150  		By("Add a new wrong-service account")
   151  		utils.Kubectl("apply", "-f", wrongSAYaml, "--kubeconfig="+kubeconfigHub)
   152  		utils.Kubectl("apply", "-f", subsetSAYaml, "--kubeconfig="+kubeconfigHub)
   153  
   154  		wrongSAToken = getToken(ctx, "default", "wrong-sa")
   155  		subsetSAToken = getToken(ctx, "default", "subset-sa")
   156  	})
   157  
   158  	Describe("Test the database migrations", func() {
   159  		It("Migrates from a clean database", func(ctx context.Context) {
   160  			tableNames, err := getTableNames(db)
   161  			Expect(err).ToNot(HaveOccurred())
   162  			Expect(tableNames).To(ContainElements("clusters", "parent_policies", "policies", "compliance_events"))
   163  
   164  			migrationVersionRows := db.QueryRow("SELECT version, dirty FROM schema_migrations")
   165  			var version int
   166  			var dirty bool
   167  			err = migrationVersionRows.Scan(&version, &dirty)
   168  			Expect(err).ToNot(HaveOccurred())
   169  			Expect(version).To(Equal(1))
   170  			Expect(dirty).To(BeFalse())
   171  		})
   172  	})
   173  
   174  	Describe("Test POSTing Events", func() {
   175  		Describe("POST one valid event with including all the optional fields", func() {
   176  			payload := []byte(`{
   177  				"cluster": {
   178  					"name": "managed1",
   179  					"cluster_id": "test1-managed1-fake-uuid-1"
   180  				},
   181  				"parent_policy": {
   182  					"name": "etcd-encryption1",
   183  					"namespace": "policies",
   184  					"categories": ["cat-1", "cat-2"],
   185  					"controls": ["ctrl-1"],
   186  					"standards": ["stand-1"]
   187  				},
   188  				"policy": {
   189  					"apiGroup": "policy.open-cluster-management.io",
   190  					"kind": "ConfigurationPolicy",
   191  					"name": "etcd-encryption1",
   192  					"namespace": "local-cluster",
   193  					"spec": {"test": "one", "severity": "low"},
   194  					"severity": "low"
   195  				},
   196  				"event": {
   197  					"compliance": "NonCompliant",
   198  					"message": "configmaps [etcd] not found in namespace default",
   199  					"timestamp": "2023-01-01T01:01:01.111Z",
   200  					"metadata": {"test": true},
   201  					"reported_by": "optional-test"
   202  				}
   203  			}`)
   204  
   205  			BeforeAll(func(ctx context.Context) {
   206  				By("POST the event")
   207  				Eventually(postEvent(ctx, payload, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
   208  			})
   209  
   210  			It("Should have created the cluster in a table", func() {
   211  				rows, err := db.Query("SELECT * FROM clusters WHERE cluster_id = $1", "test1-managed1-fake-uuid-1")
   212  				Expect(err).ToNot(HaveOccurred())
   213  
   214  				count := 0
   215  				for rows.Next() {
   216  					var (
   217  						id        int
   218  						name      string
   219  						clusterID string
   220  					)
   221  					err := rows.Scan(&id, &name, &clusterID)
   222  					Expect(err).ToNot(HaveOccurred())
   223  
   224  					Expect(id).NotTo(Equal(0))
   225  					Expect(name).To(Equal("managed1"))
   226  					count++
   227  				}
   228  
   229  				Expect(count).To(Equal(1))
   230  			})
   231  
   232  			It("Should have created the parent policy in a table", func() {
   233  				rows, err := db.Query(
   234  					"SELECT * FROM parent_policies WHERE name = $1 AND namespace= $2", "etcd-encryption1", "policies",
   235  				)
   236  				Expect(err).ToNot(HaveOccurred())
   237  
   238  				count := 0
   239  				for rows.Next() {
   240  					var (
   241  						id        int
   242  						name      string
   243  						namespace string
   244  						cats      pq.StringArray
   245  						ctrls     pq.StringArray
   246  						stands    pq.StringArray
   247  					)
   248  
   249  					err := rows.Scan(&id, &name, &namespace, &cats, &ctrls, &stands)
   250  					Expect(err).ToNot(HaveOccurred())
   251  
   252  					Expect(id).NotTo(Equal(0))
   253  					Expect(cats).To(ContainElements("cat-1", "cat-2"))
   254  					Expect(ctrls).To(ContainElements("ctrl-1"))
   255  					Expect(stands).To(ContainElements("stand-1"))
   256  					count++
   257  				}
   258  
   259  				Expect(count).To(Equal(1))
   260  			})
   261  
   262  			It("Should have created the policy in a table", func() {
   263  				rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "etcd-encryption1")
   264  				Expect(err).ToNot(HaveOccurred())
   265  
   266  				count := 0
   267  				for rows.Next() {
   268  					var (
   269  						id       int
   270  						kind     string
   271  						apiGroup string
   272  						name     string
   273  						ns       *string
   274  						spec     complianceeventsapi.JSONMap
   275  						severity *string
   276  					)
   277  
   278  					err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity)
   279  					Expect(err).ToNot(HaveOccurred())
   280  
   281  					Expect(id).NotTo(Equal(0))
   282  					Expect(kind).To(Equal("ConfigurationPolicy"))
   283  					Expect(apiGroup).To(Equal("policy.open-cluster-management.io"))
   284  					Expect(ns).ToNot(BeNil())
   285  					Expect(*ns).To(Equal("local-cluster"))
   286  					Expect(spec).ToNot(BeNil())
   287  					Expect(spec).To(BeEquivalentTo(map[string]any{"test": "one", "severity": "low"}))
   288  					Expect(severity).ToNot(BeNil())
   289  					Expect(*severity).To(Equal("low"))
   290  
   291  					count++
   292  				}
   293  
   294  				Expect(count).To(Equal(1))
   295  			})
   296  
   297  			It("Should have created the event in a table", func() {
   298  				rows, err := db.Query("SELECT * FROM compliance_events WHERE timestamp = $1",
   299  					"2023-01-01T01:01:01.111Z")
   300  				Expect(err).ToNot(HaveOccurred())
   301  
   302  				count := 0
   303  				for rows.Next() {
   304  					var (
   305  						id             int
   306  						clusterID      int
   307  						policyID       int
   308  						parentPolicyID *int
   309  						compliance     string
   310  						message        string
   311  						timestamp      string
   312  						metadata       complianceeventsapi.JSONMap
   313  						reportedBy     *string
   314  					)
   315  
   316  					err := rows.Scan(&id, &clusterID, &policyID, &parentPolicyID, &compliance, &message, &timestamp,
   317  						&metadata, &reportedBy)
   318  					Expect(err).ToNot(HaveOccurred())
   319  
   320  					Expect(id).To(Equal(1))
   321  					Expect(clusterID).To(Equal(1))
   322  					Expect(policyID).To(Equal(1))
   323  					Expect(parentPolicyID).NotTo(BeNil())
   324  					Expect(*parentPolicyID).To(Equal(1))
   325  					Expect(compliance).To(Equal("NonCompliant"))
   326  					Expect(message).To(Equal("configmaps [etcd] not found in namespace default"))
   327  					Expect(timestamp).To(Equal("2023-01-01T01:01:01.111Z"))
   328  					Expect(metadata).To(HaveKeyWithValue("test", true))
   329  					Expect(reportedBy).ToNot(BeNil())
   330  					Expect(*reportedBy).To(Equal("optional-test"))
   331  					count++
   332  				}
   333  
   334  				Expect(count).To(Equal(1))
   335  			})
   336  
   337  			It("Should return the compliance event from the API", func(ctx context.Context) {
   338  				respJSON, err := listEvents(ctx, clientToken)
   339  				Expect(err).ToNot(HaveOccurred())
   340  
   341  				complianceEvent := map[string]any{
   342  					"cluster": map[string]any{
   343  						"cluster_id": "test1-managed1-fake-uuid-1",
   344  						"name":       "managed1",
   345  					},
   346  					"event": map[string]any{
   347  						"compliance":  "NonCompliant",
   348  						"message":     "configmaps [etcd] not found in namespace default",
   349  						"metadata":    map[string]any{"test": true},
   350  						"reported_by": "optional-test",
   351  						"timestamp":   "2023-01-01T01:01:01.111Z",
   352  					},
   353  					"id": float64(1),
   354  					"parent_policy": map[string]any{
   355  						"categories": []any{"cat-1", "cat-2"},
   356  						"controls":   []any{"ctrl-1"},
   357  						"id":         float64(1),
   358  						"name":       "etcd-encryption1",
   359  						"namespace":  "policies",
   360  						"standards":  []any{"stand-1"},
   361  					},
   362  					"policy": map[string]any{
   363  						"apiGroup":  "policy.open-cluster-management.io",
   364  						"id":        float64(1),
   365  						"kind":      "ConfigurationPolicy",
   366  						"name":      "etcd-encryption1",
   367  						"namespace": "local-cluster",
   368  						"severity":  "low",
   369  					},
   370  				}
   371  
   372  				expected := map[string]any{
   373  					"data": []any{complianceEvent},
   374  					"metadata": map[string]any{
   375  						"page":     float64(1),
   376  						"pages":    float64(1),
   377  						"per_page": float64(20),
   378  						"total":    float64(1),
   379  					},
   380  				}
   381  
   382  				Expect(respJSON).To(Equal(expected))
   383  
   384  				// Get just the single compliance event
   385  				req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"/1", nil)
   386  				Expect(err).ToNot(HaveOccurred())
   387  
   388  				// Set auth token
   389  				req.Header.Set("Authorization", "Bearer "+clientToken)
   390  
   391  				resp, err := httpClient.Do(req)
   392  				Expect(err).ToNot(HaveOccurred())
   393  
   394  				defer resp.Body.Close()
   395  
   396  				body, err := io.ReadAll(resp.Body)
   397  				Expect(err).ToNot(HaveOccurred())
   398  
   399  				respJSON = map[string]any{}
   400  
   401  				err = json.Unmarshal(body, &respJSON)
   402  				Expect(err).ToNot(HaveOccurred())
   403  
   404  				complianceEvent["policy"].(map[string]any)["spec"] = map[string]any{
   405  					"severity": "low",
   406  					"test":     "one",
   407  				}
   408  
   409  				Expect(respJSON).To(Equal(complianceEvent))
   410  			})
   411  
   412  			It("Should return the compliance event with the spec from the API", func(ctx context.Context) {
   413  				respJSON, err := listEvents(ctx, clientToken, "include_spec")
   414  				Expect(err).ToNot(HaveOccurred())
   415  
   416  				data := respJSON["data"].([]any)
   417  				Expect(data).To(HaveLen(1))
   418  
   419  				spec := data[0].(map[string]any)["policy"].(map[string]any)["spec"]
   420  				expected := map[string]any{"test": "one", "severity": "low"}
   421  
   422  				Expect(spec).To(Equal(expected))
   423  			})
   424  		})
   425  
   426  		Describe("POST two minimally-valid events on different clusters and policies", func() {
   427  			payload1 := []byte(`{
   428  				"cluster": {
   429  					"name": "managed2",
   430  					"cluster_id": "test2-managed2-fake-uuid-2"
   431  				},
   432  				"policy": {
   433  					"apiGroup": "policy.open-cluster-management.io",
   434  					"kind": "ConfigurationPolicy",
   435  					"name": "etcd-encryption2",
   436  					"spec": {"test": "two"}
   437  				},
   438  				"event": {
   439  					"compliance": "NonCompliant",
   440  					"message": "configmaps [etcd] not found in namespace default",
   441  					"timestamp": "2023-02-02T02:02:02.222Z"
   442  				}
   443  			}`)
   444  
   445  			payload2 := []byte(`{
   446  				"cluster": {
   447  					"name": "managed3",
   448  					"cluster_id": "test2-managed3-fake-uuid-3"
   449  				},
   450  				"policy": {
   451  					"apiGroup": "policy.open-cluster-management.io",
   452  					"kind": "ConfigurationPolicy",
   453  					"name": "etcd-encryption2",
   454  					"spec": {"different-spec-test": "two-and-a-half"}
   455  				},
   456  				"event": {
   457  					"compliance": "Compliant",
   458  					"message": "configmaps [etcd] found in namespace default",
   459  					"timestamp": "2023-02-02T02:02:02.222Z"
   460  				}
   461  			}`)
   462  
   463  			BeforeAll(func(ctx context.Context) {
   464  				By("POST the events")
   465  				Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
   466  				Eventually(postEvent(ctx, payload2, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
   467  			})
   468  
   469  			It("Should have created both clusters in a table", func() {
   470  				rows, err := db.Query("SELECT * FROM clusters")
   471  				Expect(err).ToNot(HaveOccurred())
   472  
   473  				clusternames := make([]string, 0)
   474  
   475  				for rows.Next() {
   476  					var (
   477  						id        int
   478  						name      string
   479  						clusterID string
   480  					)
   481  					err := rows.Scan(&id, &name, &clusterID)
   482  					Expect(err).ToNot(HaveOccurred())
   483  
   484  					clusternames = append(clusternames, name)
   485  				}
   486  
   487  				Expect(clusternames).To(ContainElements("managed2", "managed3"))
   488  			})
   489  
   490  			It("Should have created two policies in a table despite having the same name", func() {
   491  				rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "etcd-encryption2")
   492  				Expect(err).ToNot(HaveOccurred())
   493  
   494  				rowCount := 0
   495  
   496  				for rows.Next() {
   497  					var (
   498  						id       int
   499  						kind     string
   500  						apiGroup string
   501  						name     string
   502  						ns       *string
   503  						spec     complianceeventsapi.JSONMap
   504  						severity *string
   505  					)
   506  
   507  					err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity)
   508  					Expect(err).ToNot(HaveOccurred())
   509  
   510  					rowCount++
   511  					Expect(id).To(Equal(1 + rowCount))
   512  				}
   513  
   514  				Expect(rowCount).To(Equal(2))
   515  			})
   516  
   517  			It("Should have created both events in a table", func() {
   518  				rows, err := db.Query("SELECT * FROM compliance_events WHERE timestamp = $1",
   519  					"2023-02-02T02:02:02.222Z")
   520  				Expect(err).ToNot(HaveOccurred())
   521  
   522  				messages := make([]string, 0)
   523  				for rows.Next() {
   524  					var (
   525  						id             int
   526  						clusterID      int
   527  						policyID       int
   528  						parentPolicyID *int
   529  						compliance     string
   530  						message        string
   531  						timestamp      string
   532  						metadata       *string
   533  						reportedBy     *string
   534  					)
   535  
   536  					err := rows.Scan(&id, &clusterID, &policyID, &parentPolicyID, &compliance, &message, &timestamp,
   537  						&metadata, &reportedBy)
   538  					Expect(err).ToNot(HaveOccurred())
   539  
   540  					messages = append(messages, message)
   541  
   542  					Expect(id).NotTo(Equal(0))
   543  					Expect(clusterID).NotTo(Equal(0))
   544  					Expect(policyID).To(Equal(1 + len(messages)))
   545  					Expect(parentPolicyID).To(BeNil())
   546  				}
   547  
   548  				Expect(messages).To(ConsistOf(
   549  					"configmaps [etcd] found in namespace default",
   550  					"configmaps [etcd] not found in namespace default",
   551  				))
   552  			})
   553  		})
   554  
   555  		Describe("API pagination", func() {
   556  			It("Should have correct default pagination", func(ctx context.Context) {
   557  				respJSON, err := listEvents(ctx, clientToken)
   558  				Expect(err).ToNot(HaveOccurred())
   559  
   560  				metadata := respJSON["metadata"].(map[string]interface{})
   561  				Expect(metadata["page"]).To(BeEquivalentTo(1))
   562  				Expect(metadata["pages"]).To(BeEquivalentTo(1))
   563  				Expect(metadata["per_page"]).To(BeEquivalentTo(20))
   564  				Expect(metadata["total"]).To(BeEquivalentTo(3))
   565  
   566  				data := respJSON["data"].([]any)
   567  				Expect(data).To(HaveLen(3))
   568  			})
   569  			It("Should have accept page=2", func(ctx context.Context) {
   570  				respJSON, err := listEvents(ctx, clientToken, "page=2")
   571  				Expect(err).ToNot(HaveOccurred())
   572  
   573  				metadata := respJSON["metadata"].(map[string]interface{})
   574  				Expect(metadata["page"]).To(BeEquivalentTo(2))
   575  				Expect(metadata["pages"]).To(BeEquivalentTo(1))
   576  				Expect(metadata["per_page"]).To(BeEquivalentTo(20))
   577  				Expect(metadata["total"]).To(BeEquivalentTo(3))
   578  
   579  				data := respJSON["data"].([]any)
   580  				Expect(data).To(BeEmpty())
   581  			})
   582  
   583  			It("Should accept per_page=2 and page=2", func(ctx context.Context) {
   584  				respJSON, err := listEvents(ctx, clientToken, "per_page=2", "page=2")
   585  				Expect(err).ToNot(HaveOccurred())
   586  
   587  				metadata := respJSON["metadata"].(map[string]interface{})
   588  				Expect(metadata["page"]).To(BeEquivalentTo(2))
   589  				Expect(metadata["pages"]).To(BeEquivalentTo(2))
   590  				Expect(metadata["per_page"]).To(BeEquivalentTo(2))
   591  				Expect(metadata["total"]).To(BeEquivalentTo(3))
   592  
   593  				data := respJSON["data"].([]any)
   594  				Expect(data).To(HaveLen(1))
   595  				// The default sort is descending order by event timestamp, so the last event in the pagination is
   596  				// the first event.
   597  				Expect(data[0].(map[string]any)["id"]).To(BeEquivalentTo(1))
   598  			})
   599  
   600  			It("Should not accept page=150", func(ctx context.Context) {
   601  				// Too many per_page
   602  				_, err := listEvents(ctx, clientToken, "per_page=150", "page=2")
   603  				Expect(err).To(HaveOccurred())
   604  				Expect(err).To(MatchError(ContainSubstring("per_page must be a value between 1 and 100")))
   605  			})
   606  
   607  			It("Should not accept per_page=-5", func(ctx context.Context) {
   608  				// Too little per_page
   609  				_, err := listEvents(ctx, clientToken, "per_page=-5", "page=2")
   610  				Expect(err).To(HaveOccurred())
   611  				Expect(err).To(MatchError(ContainSubstring("per_page must be a value between 1 and 100")))
   612  			})
   613  
   614  			It("Should not accept page=-5", func(ctx context.Context) {
   615  				// Too little per_page
   616  				_, err := listEvents(ctx, clientToken, "page=-5")
   617  				Expect(err).To(HaveOccurred())
   618  				Expect(err).To(MatchError(ContainSubstring("page must be a positive integer")))
   619  			})
   620  		})
   621  
   622  		DescribeTable("API sorting",
   623  			func(ctx context.Context, queryArgs []string, expectedIDs []float64) {
   624  				respJSON, err := listEvents(ctx, clientToken, queryArgs...)
   625  				Expect(err).ToNot(HaveOccurred())
   626  
   627  				data, ok := respJSON["data"].([]any)
   628  				Expect(ok).To(BeTrue())
   629  				Expect(data).To(HaveLen(3))
   630  
   631  				actualIDs := make([]float64, 0, 3)
   632  
   633  				for _, event := range data {
   634  					actualIDs = append(actualIDs, event.(map[string]any)["id"].(float64))
   635  				}
   636  
   637  				Expect(actualIDs).To(Equal(expectedIDs))
   638  			},
   639  			Entry(
   640  				"Sort descending by cluster.cluster_id",
   641  				[]string{"sort=cluster.cluster_id", "direction=desc"},
   642  				[]float64{3, 2, 1},
   643  			),
   644  			Entry(
   645  				"Sort ascending by cluster.cluster_id",
   646  				[]string{"sort=cluster.cluster_id", "direction=asc"},
   647  				[]float64{1, 2, 3},
   648  			),
   649  			Entry(
   650  				"Sort descending by cluster.name",
   651  				[]string{"sort=cluster.name", "direction=desc"},
   652  				[]float64{3, 2, 1},
   653  			),
   654  			Entry(
   655  				"Sort ascending by cluster.name",
   656  				[]string{"sort=cluster.name", "direction=asc"},
   657  				[]float64{1, 2, 3},
   658  			),
   659  			Entry(
   660  				"Sort descending by event.compliance",
   661  				[]string{"sort=event.compliance", "direction=desc"},
   662  				[]float64{2, 1, 3},
   663  			),
   664  			Entry(
   665  				"Sort ascending by event.compliance",
   666  				[]string{"sort=event.compliance", "direction=asc"},
   667  				[]float64{3, 1, 2},
   668  			),
   669  			Entry(
   670  				"Sort descending by event.message",
   671  				[]string{"sort=event.message", "direction=desc"},
   672  				[]float64{1, 2, 3},
   673  			),
   674  			Entry(
   675  				"Sort ascending by event.message",
   676  				[]string{"sort=event.message", "direction=asc"},
   677  				[]float64{3, 1, 2},
   678  			),
   679  			Entry(
   680  				"Sort descending by event.reported_by",
   681  				[]string{"sort=event.reported_by", "direction=desc"},
   682  				[]float64{3, 2, 1},
   683  			),
   684  			Entry(
   685  				"Sort ascending by event.reported_by",
   686  				[]string{"sort=event.reported_by", "direction=asc"},
   687  				[]float64{1, 2, 3},
   688  			),
   689  			Entry(
   690  				"Sort descending by event.timestamp (default)",
   691  				[]string{},
   692  				[]float64{3, 2, 1},
   693  			),
   694  			Entry(
   695  				"Sort descending by event.timestamp",
   696  				[]string{"sort=event.timestamp", "direction=desc"},
   697  				[]float64{3, 2, 1},
   698  			),
   699  			Entry(
   700  				"Sort ascending by event.timestamp",
   701  				[]string{"sort=event.timestamp", "direction=asc"},
   702  				[]float64{1, 2, 3},
   703  			),
   704  			Entry(
   705  				"Sort descending by parent_policy.categories",
   706  				[]string{"sort=parent_policy.categories", "direction=desc"},
   707  				[]float64{2, 3, 1},
   708  			),
   709  			Entry(
   710  				"Sort ascending by parent_policy.categories",
   711  				[]string{"sort=parent_policy.categories", "direction=asc"},
   712  				[]float64{1, 2, 3},
   713  			),
   714  			Entry(
   715  				"Sort descending by parent_policy.controls",
   716  				[]string{"sort=parent_policy.controls", "direction=desc"},
   717  				[]float64{2, 3, 1},
   718  			),
   719  			Entry(
   720  				"Sort ascending by parent_policy.controls",
   721  				[]string{"sort=parent_policy.controls", "direction=asc"},
   722  				[]float64{1, 2, 3},
   723  			),
   724  			Entry(
   725  				"Sort descending by parent_policy.id",
   726  				[]string{"sort=parent_policy.id", "direction=desc"},
   727  				[]float64{2, 3, 1},
   728  			),
   729  			Entry(
   730  				"Sort ascending by parent_policy.id",
   731  				[]string{"sort=parent_policy.id", "direction=asc"},
   732  				[]float64{1, 2, 3},
   733  			),
   734  			Entry(
   735  				"Sort descending by parent_policy.name",
   736  				[]string{"sort=parent_policy.name", "direction=desc"},
   737  				[]float64{2, 3, 1},
   738  			),
   739  			Entry(
   740  				"Sort ascending by parent_policy.name",
   741  				[]string{"sort=parent_policy.name", "direction=asc"},
   742  				[]float64{1, 2, 3},
   743  			),
   744  			Entry(
   745  				"Sort descending by parent_policy.namespace",
   746  				[]string{"sort=parent_policy.namespace", "direction=desc"},
   747  				[]float64{2, 3, 1},
   748  			),
   749  			Entry(
   750  				"Sort ascending by parent_policy.namespace",
   751  				[]string{"sort=parent_policy.namespace", "direction=asc"},
   752  				[]float64{1, 2, 3},
   753  			),
   754  			Entry(
   755  				"Sort descending by parent_policy.standards",
   756  				[]string{"sort=parent_policy.standards", "direction=desc"},
   757  				[]float64{2, 3, 1},
   758  			),
   759  			Entry(
   760  				"Sort ascending by parent_policy.standards",
   761  				[]string{"sort=parent_policy.standards", "direction=asc"},
   762  				[]float64{1, 2, 3},
   763  			),
   764  			Entry(
   765  				"Sort descending by policy.apiGroup",
   766  				[]string{"sort=policy.apiGroup", "direction=desc"},
   767  				[]float64{1, 2, 3},
   768  			),
   769  			Entry(
   770  				"Sort ascending by policy.apiGroup",
   771  				[]string{"sort=policy.apiGroup", "direction=asc"},
   772  				[]float64{1, 2, 3},
   773  			),
   774  			Entry(
   775  				"Sort descending by policy.id",
   776  				[]string{"sort=policy.id", "direction=desc"},
   777  				[]float64{3, 2, 1},
   778  			),
   779  			Entry(
   780  				"Sort ascending by policy.id",
   781  				[]string{"sort=policy.id", "direction=asc"},
   782  				[]float64{1, 2, 3},
   783  			),
   784  			Entry(
   785  				"Sort descending by policy.kind",
   786  				[]string{"sort=policy.kind", "direction=desc"},
   787  				[]float64{1, 2, 3},
   788  			),
   789  			Entry(
   790  				"Sort ascending by policy.kind",
   791  				[]string{"sort=policy.kind", "direction=asc"},
   792  				[]float64{1, 2, 3},
   793  			),
   794  			Entry(
   795  				"Sort descending by policy.name",
   796  				[]string{"sort=policy.name", "direction=desc"},
   797  				[]float64{2, 3, 1},
   798  			),
   799  			Entry(
   800  				"Sort ascending by policy.name",
   801  				[]string{"sort=policy.name", "direction=asc"},
   802  				[]float64{1, 2, 3},
   803  			),
   804  			Entry(
   805  				"Sort descending by policy.namespace",
   806  				[]string{"sort=policy.namespace", "direction=desc"},
   807  				[]float64{2, 3, 1},
   808  			),
   809  			Entry(
   810  				"Sort ascending by policy.namespace",
   811  				[]string{"sort=policy.namespace", "direction=asc"},
   812  				[]float64{1, 2, 3},
   813  			),
   814  			Entry(
   815  				"Sort descending by policy.severity",
   816  				[]string{"sort=policy.severity", "direction=desc"},
   817  				[]float64{2, 3, 1},
   818  			),
   819  			Entry(
   820  				"Sort ascending by policy.severity",
   821  				[]string{"sort=policy.severity", "direction=asc"},
   822  				[]float64{1, 2, 3},
   823  			),
   824  			Entry(
   825  				"Sort descending by parent_policy.id and policy.id",
   826  				[]string{"sort=parent_policy.id,policy.id", "direction=asc"},
   827  				[]float64{1, 2, 3},
   828  			),
   829  			Entry(
   830  				"Sort descending by id",
   831  				[]string{"sort=id", "direction=desc"},
   832  				[]float64{3, 2, 1},
   833  			),
   834  			Entry(
   835  				"Sort ascending by id",
   836  				[]string{"sort=id", "direction=asc"},
   837  				[]float64{1, 2, 3},
   838  			),
   839  		)
   840  
   841  		Describe("Invalid event ID", func() {
   842  			It("Compliance event is not found", func(ctx context.Context) {
   843  				req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"/1231291", nil)
   844  				Expect(err).ToNot(HaveOccurred())
   845  
   846  				// Set auth token
   847  				req.Header.Set("Authorization", "Bearer "+clientToken)
   848  
   849  				resp, err := httpClient.Do(req)
   850  				Expect(err).ToNot(HaveOccurred())
   851  
   852  				defer resp.Body.Close()
   853  
   854  				Expect(resp.StatusCode).To(Equal(http.StatusNotFound))
   855  
   856  				body, err := io.ReadAll(resp.Body)
   857  				Expect(err).ToNot(HaveOccurred())
   858  
   859  				respJSON := map[string]any{}
   860  
   861  				err = json.Unmarshal(body, &respJSON)
   862  				Expect(err).ToNot(HaveOccurred())
   863  
   864  				Expect(respJSON["message"].(string)).To(Equal("The requested compliance event was not found"))
   865  			})
   866  
   867  			It("Compliance event ID is invalid", func(ctx context.Context) {
   868  				req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"/sql-injections-lose", nil)
   869  				Expect(err).ToNot(HaveOccurred())
   870  
   871  				// Set auth token
   872  				req.Header.Set("Authorization", "Bearer "+clientToken)
   873  
   874  				resp, err := httpClient.Do(req)
   875  				Expect(err).ToNot(HaveOccurred())
   876  
   877  				defer resp.Body.Close()
   878  
   879  				Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))
   880  
   881  				body, err := io.ReadAll(resp.Body)
   882  				Expect(err).ToNot(HaveOccurred())
   883  
   884  				respJSON := map[string]any{}
   885  
   886  				err = json.Unmarshal(body, &respJSON)
   887  				Expect(err).ToNot(HaveOccurred())
   888  
   889  				Expect(respJSON["message"].(string)).To(Equal("The provided compliance event ID is invalid"))
   890  			})
   891  		})
   892  
   893  		Describe("Invalid sort options", func() {
   894  			It("An invalid sort of sort=my-laundry", func(ctx context.Context) {
   895  				_, err := listEvents(ctx, clientToken, "sort=my-laundry")
   896  				Expect(err).To(HaveOccurred())
   897  				expected := "an invalid sort option was provided, choose from: cluster.cluster_id, cluster.name, " +
   898  					"event.compliance, event.message, event.reported_by, event.timestamp, id, " +
   899  					"parent_policy.categories, parent_policy.controls, parent_policy.id, parent_policy.name, " +
   900  					"parent_policy.namespace, parent_policy.standards, policy.apiGroup, policy.id, policy.kind, " +
   901  					"policy.name, policy.namespace, policy.severity"
   902  				Expect(err).To(MatchError(ContainSubstring(expected)))
   903  			})
   904  
   905  			It("An invalid sort direction", func(ctx context.Context) {
   906  				_, err := listEvents(ctx, clientToken, "direction=up")
   907  				Expect(err).To(HaveOccurred())
   908  				Expect(err).To(MatchError(ContainSubstring("direction must be one of: asc, desc")))
   909  			})
   910  		})
   911  
   912  		Describe("Invalid query arguments", func() {
   913  			It("An invalid query argument", func(ctx context.Context) {
   914  				_, err := listEvents(ctx, clientToken, "make_it_compliant=please")
   915  				expected := "an invalid query argument was provided, choose from: cluster.cluster_id, cluster.name, " +
   916  					"direction, event.compliance, event.message, event.message_includes, event.message_like, " +
   917  					"event.reported_by, event.timestamp, event.timestamp_after, event.timestamp_before, id, " +
   918  					"include_spec, page, parent_policy.categories, parent_policy.controls, parent_policy.id, " +
   919  					"parent_policy.name, parent_policy.namespace, parent_policy.standards, per_page, " +
   920  					"policy.apiGroup, policy.id, policy.kind, policy.name, policy.namespace, policy.severity, sort"
   921  				Expect(err).To(HaveOccurred())
   922  				Expect(err).To(MatchError(ContainSubstring(expected)))
   923  			})
   924  
   925  			It("An invalid include_spec=yes-please", func(ctx context.Context) {
   926  				_, err := listEvents(ctx, clientToken, "include_spec=yes-please")
   927  				Expect(err).To(HaveOccurred())
   928  				Expect(err).To(MatchError(ContainSubstring("include_spec is a flag and does not accept a value")))
   929  			})
   930  
   931  			It("An invalid sort direction", func(ctx context.Context) {
   932  				_, err := listEvents(ctx, clientToken, "direction=up")
   933  				Expect(err).To(HaveOccurred())
   934  				Expect(err).To(MatchError(ContainSubstring("direction must be one of: asc, desc")))
   935  			})
   936  		})
   937  
   938  		Describe("POST three events on the same cluster and policy", func() {
   939  			// payload1 defines most things, and should cause the cluster, parent, and policy to be created.
   940  			payload1 := []byte(`{
   941  				"cluster": {
   942  					"name": "managed4",
   943  					"cluster_id": "test3-managed4-fake-uuid-4"
   944  				},
   945  				"parent_policy": {
   946  					"name": "common-parent",
   947  					"namespace": "policies",
   948  					"categories": ["cat-3", "cat-4"],
   949  					"controls": ["ctrl-2"],
   950  					"standards": ["stand-2"]
   951  				},
   952  				"policy": {
   953  					"apiGroup": "policy.open-cluster-management.io",
   954  					"kind": "ConfigurationPolicy",
   955  					"name": "common",
   956  					"spec": {"test": "three", "severity": "low"},
   957  					"severity": "low"
   958  				},
   959  				"event": {
   960  					"compliance": "NonCompliant",
   961  					"message": "configmaps [common] not found in namespace default",
   962  					"timestamp": "2023-03-03T03:03:03.333Z"
   963  				}
   964  			}`)
   965  
   966  			// payload2 just uses the ids for the policy and parent_policy.
   967  			payload2 := []byte(`{
   968  				"cluster": {
   969  					"name": "managed4",
   970  					"cluster_id": "test3-managed4-fake-uuid-4"
   971  				},
   972  				"parent_policy": {
   973  					"id": 2
   974  				},
   975  				"policy": {
   976  					"id": 4
   977  				},
   978  				"event": {
   979  					"compliance": "NonCompliant",
   980  					"message": "configmaps [common] not found in namespace default",
   981  					"timestamp": "2023-04-04T04:04:04.444Z"
   982  				}
   983  			}`)
   984  
   985  			// payload3 redefines most things, and should cause the cluster, parent, and policy to be reused from the
   986  			// cache.
   987  			payload3 := []byte(`{
   988  				"cluster": {
   989  					"name": "managed4",
   990  					"cluster_id": "test3-managed4-fake-uuid-4"
   991  				},
   992  				"parent_policy": {
   993  					"name": "common-parent",
   994  					"namespace": "policies",
   995  					"categories": ["cat-3", "cat-4"],
   996  					"controls": ["ctrl-2"],
   997  					"standards": ["stand-2"]
   998  				},
   999  				"policy": {
  1000  					"apiGroup": "policy.open-cluster-management.io",
  1001  					"kind": "ConfigurationPolicy",
  1002  					"name": "common",
  1003  					"spec": {"test": "three", "severity": "low"},
  1004  					"severity": "low"
  1005  				},
  1006  				"event": {
  1007  					"compliance": "NonCompliant",
  1008  					"message": "configmaps [common] not found in namespace default",
  1009  					"timestamp": "2023-05-05T05:05:05.555Z"
  1010  				}
  1011  			}`)
  1012  
  1013  			BeforeAll(func(ctx context.Context) {
  1014  				By("POST the events")
  1015  				Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  1016  				Eventually(postEvent(ctx, payload2, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  1017  				Eventually(postEvent(ctx, payload3, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  1018  			})
  1019  
  1020  			It("Should have only created one cluster in the table", func() {
  1021  				rows, err := db.Query("SELECT * FROM clusters WHERE name = $1", "managed4")
  1022  				Expect(err).ToNot(HaveOccurred())
  1023  
  1024  				count := 0
  1025  				for rows.Next() {
  1026  					var (
  1027  						id        int
  1028  						name      string
  1029  						clusterID string
  1030  					)
  1031  					err := rows.Scan(&id, &name, &clusterID)
  1032  					Expect(err).ToNot(HaveOccurred())
  1033  
  1034  					Expect(id).NotTo(Equal(0))
  1035  					count++
  1036  				}
  1037  
  1038  				Expect(count).To(Equal(1))
  1039  			})
  1040  
  1041  			It("Should have only created one parent policy in a table", func() {
  1042  				rows, err := db.Query(
  1043  					"SELECT * FROM parent_policies WHERE name = $1 AND namespace = $2", "common-parent", "policies",
  1044  				)
  1045  				Expect(err).ToNot(HaveOccurred())
  1046  
  1047  				count := 0
  1048  				for rows.Next() {
  1049  					var (
  1050  						id        int
  1051  						name      string
  1052  						namespace string
  1053  						cats      pq.StringArray
  1054  						ctrls     pq.StringArray
  1055  						stands    pq.StringArray
  1056  					)
  1057  
  1058  					err := rows.Scan(&id, &name, &namespace, &cats, &ctrls, &stands)
  1059  					Expect(err).ToNot(HaveOccurred())
  1060  
  1061  					Expect(id).NotTo(Equal(0))
  1062  					count++
  1063  				}
  1064  
  1065  				Expect(count).To(Equal(1))
  1066  			})
  1067  
  1068  			It("Should have only created one policy in a table", func() {
  1069  				rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "common")
  1070  				Expect(err).ToNot(HaveOccurred())
  1071  
  1072  				specs := make([]complianceeventsapi.JSONMap, 0, 1)
  1073  				for rows.Next() {
  1074  					var (
  1075  						id       int
  1076  						kind     string
  1077  						apiGroup string
  1078  						name     string
  1079  						ns       *string
  1080  						spec     complianceeventsapi.JSONMap
  1081  						severity *string
  1082  					)
  1083  
  1084  					err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity)
  1085  					Expect(err).ToNot(HaveOccurred())
  1086  
  1087  					Expect(id).NotTo(Equal(0))
  1088  					specs = append(specs, spec)
  1089  				}
  1090  
  1091  				Expect(specs).To(HaveLen(1))
  1092  				Expect(specs[0]).To(BeEquivalentTo(map[string]any{"test": "three", "severity": "low"}))
  1093  			})
  1094  
  1095  			It("Should have created three events in a table", func() {
  1096  				rows, err := db.Query("SELECT * FROM compliance_events WHERE message = $1",
  1097  					"configmaps [common] not found in namespace default")
  1098  				Expect(err).ToNot(HaveOccurred())
  1099  
  1100  				timestamps := make([]string, 0, 3)
  1101  				for rows.Next() {
  1102  					var (
  1103  						id             int
  1104  						clusterID      int
  1105  						policyID       int
  1106  						parentPolicyID *int
  1107  						compliance     string
  1108  						message        string
  1109  						timestamp      string
  1110  						metadata       *string
  1111  						reportedBy     *string
  1112  					)
  1113  
  1114  					err := rows.Scan(&id, &clusterID, &policyID, &parentPolicyID, &compliance, &message, &timestamp,
  1115  						&metadata, &reportedBy)
  1116  					Expect(err).ToNot(HaveOccurred())
  1117  
  1118  					Expect(id).NotTo(Equal(0))
  1119  					Expect(clusterID).NotTo(Equal(0))
  1120  					Expect(policyID).To(Equal(4))
  1121  					Expect(parentPolicyID).NotTo(BeNil())
  1122  					Expect(*parentPolicyID).To(Equal(2))
  1123  
  1124  					timestamps = append(timestamps, timestamp)
  1125  				}
  1126  
  1127  				Expect(timestamps).To(ConsistOf(
  1128  					"2023-03-03T03:03:03.333Z",
  1129  					"2023-04-04T04:04:04.444Z",
  1130  					"2023-05-05T05:05:05.555Z",
  1131  				))
  1132  			})
  1133  		})
  1134  
  1135  		Describe("POST events to check parent policy matching", func() {
  1136  			// payload1 defines most things, and should cause the cluster, parent, and policy to be created.
  1137  			payload1 := []byte(`{
  1138  				"cluster": {
  1139  					"name": "managed5",
  1140  					"cluster_id": "test5-managed5-fake-uuid-5"
  1141  				},
  1142  				"parent_policy": {
  1143  					"name": "parent-a",
  1144  					"namespace": "policies",
  1145  					"standards": ["stand-3"]
  1146  				},
  1147  				"policy": {
  1148  					"apiGroup": "policy.open-cluster-management.io",
  1149  					"kind": "ConfigurationPolicy",
  1150  					"name": "common-a",
  1151  					"spec": {"test": "four", "severity": "low"},
  1152  					"severity": "low"
  1153  				},
  1154  				"event": {
  1155  					"compliance": "Compliant",
  1156  					"message": "configmaps [common] found in namespace default",
  1157  					"timestamp": "2023-05-05T05:05:05.555Z"
  1158  				}
  1159  			}`)
  1160  
  1161  			// payload2 skips the standards array on the parent policy,
  1162  			// which should create a new parent policy
  1163  			payload2 := []byte(`{
  1164  				"cluster": {
  1165  					"name": "managed5",
  1166  					"cluster_id": "test5-managed5-fake-uuid-5"
  1167  				},
  1168  				"parent_policy": {
  1169  					"name": "parent-a",
  1170  					"namespace": "policies"
  1171  				},
  1172  				"policy": {
  1173  					"apiGroup": "policy.open-cluster-management.io",
  1174  					"kind": "ConfigurationPolicy",
  1175  					"name": "common-a",
  1176  					"spec": {"test": "four", "severity": "low"},
  1177  					"severity": "low"
  1178  				},
  1179  				"event": {
  1180  					"compliance": "Compliant",
  1181  					"message": "configmaps [common] found in namespace default",
  1182  					"timestamp": "2023-06-06T06:06:06.666Z"
  1183  				}
  1184  			}`)
  1185  
  1186  			// payload3 defines the standards with an empty array,
  1187  			// which should be the same as not specifying it at all (payload2)
  1188  			payload3 := []byte(`{
  1189  				"cluster": {
  1190  					"name": "managed5",
  1191  					"cluster_id": "test5-managed5-fake-uuid-5"
  1192  				},
  1193  				"parent_policy": {
  1194  					"name": "parent-a",
  1195  					"namespace": "policies",
  1196  					"standards": []
  1197  				},
  1198  				"policy": {
  1199  					"apiGroup": "policy.open-cluster-management.io",
  1200  					"kind": "ConfigurationPolicy",
  1201  					"name": "common-a",
  1202  					"spec": {"test": "four", "severity": "low"},
  1203  					"severity": "low"
  1204  				},
  1205  				"event": {
  1206  					"compliance": "Compliant",
  1207  					"message": "configmaps [common] found in namespace default",
  1208  					"timestamp": "2023-07-07T07:07:07.777Z"
  1209  				}
  1210  			}`)
  1211  
  1212  			BeforeAll(func(ctx context.Context) {
  1213  				By("POST the events")
  1214  				Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  1215  				Eventually(postEvent(ctx, payload2, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  1216  				Eventually(postEvent(ctx, payload3, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  1217  			})
  1218  
  1219  			It("Should have created two parent policies", func() {
  1220  				rows, err := db.Query(
  1221  					"SELECT * FROM parent_policies WHERE name = $1 AND namespace = $2", "parent-a", "policies",
  1222  				)
  1223  				Expect(err).ToNot(HaveOccurred())
  1224  
  1225  				standardArrays := make([]pq.StringArray, 0)
  1226  				for rows.Next() {
  1227  					var (
  1228  						id        int
  1229  						name      string
  1230  						namespace string
  1231  						cats      pq.StringArray
  1232  						ctrls     pq.StringArray
  1233  						stands    pq.StringArray
  1234  					)
  1235  
  1236  					err := rows.Scan(&id, &name, &namespace, &cats, &ctrls, &stands)
  1237  					Expect(err).ToNot(HaveOccurred())
  1238  
  1239  					Expect(id).NotTo(Equal(0))
  1240  					standardArrays = append(standardArrays, stands)
  1241  				}
  1242  
  1243  				Expect(standardArrays).To(ConsistOf(
  1244  					pq.StringArray{"stand-3"},
  1245  					nil,
  1246  				))
  1247  			})
  1248  
  1249  			It("Should have created a single policy", func() {
  1250  				rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "common-a")
  1251  				Expect(err).ToNot(HaveOccurred())
  1252  
  1253  				ids := make([]int, 0)
  1254  				for rows.Next() {
  1255  					var (
  1256  						id       int
  1257  						kind     string
  1258  						apiGroup string
  1259  						name     string
  1260  						ns       *string
  1261  						spec     complianceeventsapi.JSONMap
  1262  						severity *string
  1263  					)
  1264  
  1265  					err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity)
  1266  					Expect(err).ToNot(HaveOccurred())
  1267  
  1268  					Expect(id).NotTo(Equal(0))
  1269  					ids = append(ids, id)
  1270  				}
  1271  
  1272  				Expect(ids).To(HaveLen(1))
  1273  			})
  1274  		})
  1275  
  1276  		Describe("POST events to check policy namespace matching", func() {
  1277  			// payload1 should cause the cluster, parent, and policy to be created.
  1278  			payload1 := []byte(`{
  1279  				"cluster": {
  1280  					"name": "managed6",
  1281  					"cluster_id": "test6-managed6-fake-uuid-6"
  1282  				},
  1283  				"parent_policy": {
  1284  					"name": "parent-b",
  1285  					"namespace": "policies"
  1286  				},
  1287  				"policy": {
  1288  					"apiGroup": "policy.open-cluster-management.io",
  1289  					"kind": "ConfigurationPolicy",
  1290  					"name": "common-b",
  1291  					"spec": {"test": "four", "severity": "low"},
  1292  					"severity": "low",
  1293  					"namespace": "default"
  1294  				},
  1295  				"event": {
  1296  					"compliance": "Compliant",
  1297  					"message": "configmaps [common] found in namespace default",
  1298  					"timestamp": "2023-01-02T03:04:05.111Z"
  1299  				}
  1300  			}`)
  1301  
  1302  			// payload2 skips the namespace, which should create a new policy
  1303  			payload2 := []byte(`{
  1304  				"cluster": {
  1305  					"name": "managed6",
  1306  					"cluster_id": "test6-managed6-fake-uuid-6"
  1307  				},
  1308  				"parent_policy": {
  1309  					"name": "parent-b",
  1310  					"namespace": "policies"
  1311  				},
  1312  				"policy": {
  1313  					"apiGroup": "policy.open-cluster-management.io",
  1314  					"kind": "ConfigurationPolicy",
  1315  					"name": "common-b",
  1316  					"spec": {"test": "four", "severity": "low"},
  1317  					"severity": "low"
  1318  				},
  1319  				"event": {
  1320  					"compliance": "Compliant",
  1321  					"message": "configmaps [common] found in namespace default",
  1322  					"timestamp": "2023-01-02T03:04:05.222Z"
  1323  				}
  1324  			}`)
  1325  
  1326  			BeforeAll(func(ctx context.Context) {
  1327  				By("POST the events")
  1328  				Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  1329  				Eventually(postEvent(ctx, payload2, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  1330  			})
  1331  
  1332  			It("Should have created one parent policy", func() {
  1333  				rows, err := db.Query(
  1334  					"SELECT * FROM parent_policies WHERE name = $1 AND namespace = $2", "parent-b", "policies",
  1335  				)
  1336  				Expect(err).ToNot(HaveOccurred())
  1337  
  1338  				count := 0
  1339  				for rows.Next() {
  1340  					var (
  1341  						id        int
  1342  						name      string
  1343  						namespace string
  1344  						cats      pq.StringArray
  1345  						ctrls     pq.StringArray
  1346  						stands    pq.StringArray
  1347  					)
  1348  
  1349  					err := rows.Scan(&id, &name, &namespace, &cats, &ctrls, &stands)
  1350  					Expect(err).ToNot(HaveOccurred())
  1351  					Expect(id).NotTo(Equal(0))
  1352  					count++
  1353  				}
  1354  
  1355  				Expect(count).To(Equal(1))
  1356  			})
  1357  
  1358  			It("Should have created two policies in the table, with different namespaces", func() {
  1359  				rows, err := db.Query("SELECT * FROM policies WHERE name = $1", "common-b")
  1360  				Expect(err).ToNot(HaveOccurred())
  1361  
  1362  				ids := make([]int, 0)
  1363  				names := make([]string, 0)
  1364  				namespaces := make([]string, 0)
  1365  				specs := make([]complianceeventsapi.JSONMap, 0, 2)
  1366  				for rows.Next() {
  1367  					var (
  1368  						id       int
  1369  						kind     string
  1370  						apiGroup string
  1371  						name     string
  1372  						ns       *string
  1373  						spec     complianceeventsapi.JSONMap
  1374  						severity *string
  1375  					)
  1376  
  1377  					err := rows.Scan(&id, &kind, &apiGroup, &name, &ns, &spec, &severity)
  1378  					Expect(err).ToNot(HaveOccurred())
  1379  
  1380  					Expect(id).NotTo(Equal(0))
  1381  					ids = append(ids, id)
  1382  					names = append(names, name)
  1383  					specs = append(specs, spec)
  1384  
  1385  					if ns != nil {
  1386  						namespaces = append(namespaces, *ns)
  1387  					}
  1388  				}
  1389  
  1390  				Expect(ids).To(HaveLen(2))
  1391  				Expect(ids[0]).ToNot(Equal(ids[1]))
  1392  				Expect(names[0]).To(Equal(names[1]))
  1393  				Expect(namespaces).To(ConsistOf("default"))
  1394  				Expect(specs[0]).To(Equal(specs[1]))
  1395  			})
  1396  		})
  1397  
  1398  		Describe("POST invalid events", func() {
  1399  			It("should require the cluster to be specified", func(ctx context.Context) {
  1400  				Eventually(postEvent(ctx, []byte(`{
  1401  					"parent_policy": {
  1402  						"name": "validity-parent",
  1403  						"namespace": "policies"
  1404  					},
  1405  					"policy": {
  1406  						"apiGroup": "policy.open-cluster-management.io",
  1407  						"kind": "ConfigurationPolicy",
  1408  						"name": "validity",
  1409  						"spec": {"test":"validity", "severity": "low"}
  1410  					},
  1411  					"event": {
  1412  						"compliance": "Compliant",
  1413  						"message": "configmaps [valid] valid in namespace valid",
  1414  						"timestamp": "2023-09-09T09:09:09.999Z"
  1415  					}
  1416  				}`), clientToken), "5s", "1s").Should(
  1417  					MatchError(ContainSubstring("Got non-201 status code 400")),
  1418  				)
  1419  			})
  1420  
  1421  			It("should require the parent policy namespace to be specified", func(ctx context.Context) {
  1422  				Eventually(postEvent(ctx, []byte(`{
  1423  					"cluster": {
  1424  						"name": "validity-test",
  1425  						"cluster_id": "test-validity-fake-uuid"
  1426  					},
  1427  					"parent_policy": {
  1428  						"name": "validity-parent"
  1429  					},
  1430  					"policy": {
  1431  						"apiGroup": "policy.open-cluster-management.io",
  1432  						"kind": "ConfigurationPolicy",
  1433  						"name": "validity",
  1434  						"spec": {"test":"validity", "severity": "low"},
  1435  						"severity": "low"
  1436  					},
  1437  					"event": {
  1438  						"compliance": "Compliant",
  1439  						"message": "configmaps [valid] valid in namespace valid",
  1440  						"timestamp": "2023-09-09T09:09:09.999Z"
  1441  					}
  1442  				}`), clientToken), "5s", "1s").Should(
  1443  					MatchError(ContainSubstring("Got non-201 status code 400")),
  1444  				)
  1445  			})
  1446  
  1447  			It("should require the event time to be specified", func(ctx context.Context) {
  1448  				Eventually(postEvent(ctx, []byte(`{
  1449  					"cluster": {
  1450  						"name": "validity-test",
  1451  						"cluster_id": "test-validity-fake-uuid"
  1452  					},
  1453  					"parent_policy": {
  1454  						"name": "validity-parent",
  1455  						"namespace": "policies"
  1456  					},
  1457  					"policy": {
  1458  						"apiGroup": "policy.open-cluster-management.io",
  1459  						"kind": "ConfigurationPolicy",
  1460  						"name": "validity",
  1461  						"spec": {"test": "validity", "severity": "low"},
  1462  						"severity": "low"
  1463  					},
  1464  					"event": {
  1465  						"compliance": "Compliant",
  1466  						"message": "configmaps [valid] valid in namespace valid"
  1467  					}
  1468  				}`), clientToken), "5s", "1s").Should(
  1469  					MatchError(ContainSubstring("Got non-201 status code 400")),
  1470  				)
  1471  			})
  1472  
  1473  			It("should require the parent policy to have fields when specified", func(ctx context.Context) {
  1474  				Eventually(postEvent(ctx, []byte(`{
  1475  					"cluster": {
  1476  						"name": "validity-test",
  1477  						"cluster_id": "test-validity-fake-uuid"
  1478  					},
  1479  					"parent_policy": {},
  1480  					"policy": {
  1481  						"apiGroup": "policy.open-cluster-management.io",
  1482  						"kind": "ConfigurationPolicy",
  1483  						"name": "validity",
  1484  						"spec": {"test": "validity", "severity": "low"},
  1485  						"severity": "low"
  1486  					},
  1487  					"event": {
  1488  						"compliance": "Compliant",
  1489  						"message": "configmaps [valid] valid in namespace valid",
  1490  						"timestamp": "2023-09-09T09:09:09.999Z"
  1491  					}
  1492  				}`), clientToken), "5s", "1s").Should(
  1493  					MatchError(ContainSubstring("Got non-201 status code 400")),
  1494  				)
  1495  			})
  1496  
  1497  			It("should require the policy to be defined", func(ctx context.Context) {
  1498  				Eventually(postEvent(ctx, []byte(`{
  1499  					"cluster": {
  1500  						"name": "validity-test",
  1501  						"cluster_id": "test-validity-fake-uuid"
  1502  					},
  1503  					"parent_policy": {
  1504  						"name": "validity-parent",
  1505  						"namespace": "policies"
  1506  					},
  1507  					"policy": {},
  1508  					"event": {
  1509  						"compliance": "Compliant",
  1510  						"message": "configmaps [valid] valid in namespace valid",
  1511  						"timestamp": "2023-09-09T09:09:09.999Z"
  1512  					}
  1513  				}`), clientToken), "5s", "1s").Should(
  1514  					MatchError(ContainSubstring("Got non-201 status code 400")),
  1515  				)
  1516  			})
  1517  
  1518  			It("should require the input to be valid JSON", func(ctx context.Context) {
  1519  				Eventually(postEvent(ctx, []byte(`{
  1520  					foo: bar: baz
  1521  					"cluster": {
  1522  						"name": "validity-test",
  1523  						"cluster_id": "test-validity-fake-uuid"
  1524  					},
  1525  					"parent_policy": {
  1526  						"name": "validity-parent",
  1527  						"namespace": "policies"
  1528  					},
  1529  					"policy": {
  1530  						"apiGroup": "policy.open-cluster-management.io",
  1531  						"kind": "ConfigurationPolicy",
  1532  						"name": "validity",
  1533  						"spec": {"test": "validity", "severity": "low"},
  1534  						"severity": "low",
  1535  						"specHash": "foobar"
  1536  					},
  1537  					"event": {
  1538  						"compliance": "Compliant",
  1539  						"message": "configmaps [valid] valid in namespace valid",
  1540  						"timestamp": "2023-09-09T09:09:09.999Z"
  1541  					}
  1542  				}`), clientToken), "5s", "1s").Should(
  1543  					MatchError(ContainSubstring("Got non-201 status code 400")),
  1544  				)
  1545  			})
  1546  
  1547  			It("should require the spec when inputting a new policy", func(ctx context.Context) {
  1548  				Eventually(postEvent(ctx, []byte(`{
  1549  					"cluster": {
  1550  						"name": "validity-test",
  1551  						"cluster_id": "test-validity-fake-uuid"
  1552  					},
  1553  					"parent_policy": {
  1554  						"id": 1231234
  1555  					},
  1556  					"policy": {
  1557  						"id": 123123
  1558  					},
  1559  					"event": {
  1560  						"compliance": "Compliant",
  1561  						"message": "configmaps [valid] valid in namespace valid",
  1562  						"timestamp": "2023-09-09T09:09:09.999Z"
  1563  					}
  1564  				}`), clientToken), "5s", "1s").Should(MatchError(ContainSubstring(
  1565  					`invalid input: parent_policy.id not found\\ninvalid input: policy.id not found`,
  1566  				)))
  1567  			})
  1568  		})
  1569  
  1570  		DescribeTable("API filtering",
  1571  			func(ctx context.Context, queryArgs []string, expectedIDs []float64) {
  1572  				respJSON, err := listEvents(ctx, clientToken, queryArgs...)
  1573  				Expect(err).ToNot(HaveOccurred())
  1574  
  1575  				data, ok := respJSON["data"].([]any)
  1576  				Expect(ok).To(BeTrue())
  1577  
  1578  				actualIDs := []float64{}
  1579  
  1580  				for _, event := range data {
  1581  					actualIDs = append(actualIDs, event.(map[string]any)["id"].(float64))
  1582  				}
  1583  
  1584  				Expect(actualIDs).To(Equal(expectedIDs))
  1585  			},
  1586  			Entry(
  1587  				"Filter by cluster.cluster_id",
  1588  				[]string{"cluster.cluster_id=test1-managed1-fake-uuid-1,test6-managed6-fake-uuid-6"},
  1589  				[]float64{11, 10, 1},
  1590  			),
  1591  			Entry(
  1592  				"Filter by cluster.name",
  1593  				[]string{"cluster.name=managed1,managed6"},
  1594  				[]float64{11, 10, 1},
  1595  			),
  1596  			Entry(
  1597  				"Filter by event.compliance",
  1598  				[]string{"event.compliance=Compliant"},
  1599  				[]float64{9, 8, 7, 3, 11, 10},
  1600  			),
  1601  			Entry(
  1602  				"Filter by event.message",
  1603  				[]string{"event.message=configmaps%20%5Bcommon%5D%20not%20found%20in%20namespace%20default"},
  1604  				[]float64{6, 5, 4},
  1605  			),
  1606  			Entry(
  1607  				"Filter by event.message_includes",
  1608  				[]string{"event.message_includes=etcd"},
  1609  				[]float64{2, 3, 1},
  1610  			),
  1611  			Entry(
  1612  				"Filter by event.message_includes and ensure special characters are escaped",
  1613  				[]string{"event.message_includes=co_m%25n"},
  1614  				[]float64{},
  1615  			),
  1616  			Entry(
  1617  				"Filter by event.message_like",
  1618  				[]string{"event.message_like=configmaps%20%5B%25common%25%5D%25"},
  1619  				[]float64{9, 8, 6, 7, 5, 4, 11, 10},
  1620  			),
  1621  			Entry(
  1622  				"Filter by event.timestamp",
  1623  				[]string{"event.timestamp=2023-01-01T01:01:01.111Z"},
  1624  				[]float64{1},
  1625  			),
  1626  			Entry(
  1627  				"Filter by event.timestamp_after",
  1628  				[]string{"event.timestamp_after=2023-04-01T01:01:01.111Z"},
  1629  				[]float64{9, 8, 7, 6, 5},
  1630  			),
  1631  			Entry(
  1632  				"Filter by event.timestamp_before",
  1633  				[]string{"event.timestamp_before=2023-04-01T01:01:01.111Z"},
  1634  				[]float64{4, 3, 2, 11, 10, 1},
  1635  			),
  1636  			Entry(
  1637  				"Filter by event.timestamp_after and event.timestamp_before",
  1638  				[]string{
  1639  					"event.timestamp_after=2023-01-01T01:01:01.111Z", "event.timestamp_before=2023-04-01T01:01:01.111Z",
  1640  				},
  1641  				[]float64{4, 2, 3, 11, 10},
  1642  			),
  1643  			Entry(
  1644  				"Filter by parent_policy.categories",
  1645  				[]string{"parent_policy.categories=cat-1,cat-3"},
  1646  				[]float64{6, 5, 4, 1},
  1647  			),
  1648  			Entry(
  1649  				"Filter by parent_policy.categories is null",
  1650  				[]string{"parent_policy.categories"},
  1651  				[]float64{9, 8, 7, 2, 3, 11, 10},
  1652  			),
  1653  			Entry(
  1654  				"Filter by parent_policy.controls",
  1655  				[]string{"parent_policy.controls=ctrl-2"},
  1656  				[]float64{6, 5, 4},
  1657  			),
  1658  			Entry(
  1659  				"Filter by parent_policy.controls is null",
  1660  				[]string{"parent_policy.controls"},
  1661  				[]float64{9, 8, 7, 2, 3, 11, 10},
  1662  			),
  1663  			Entry(
  1664  				"Filter by parent_policy.id",
  1665  				[]string{"parent_policy.id=2"},
  1666  				[]float64{6, 5, 4},
  1667  			),
  1668  			Entry(
  1669  				"Filter by parent_policy.name",
  1670  				[]string{"parent_policy.name=etcd-encryption1"},
  1671  				[]float64{1},
  1672  			),
  1673  			Entry(
  1674  				"Filter by parent_policy.namespace",
  1675  				[]string{"parent_policy.namespace=policies"},
  1676  				[]float64{9, 8, 6, 7, 5, 4, 11, 10, 1},
  1677  			),
  1678  			Entry(
  1679  				"Filter by parent_policy.standards",
  1680  				[]string{"parent_policy.standards=stand-2"},
  1681  				[]float64{6, 5, 4},
  1682  			),
  1683  			Entry(
  1684  				"Filter by parent_policy.standards is null",
  1685  				[]string{"parent_policy.standards"},
  1686  				[]float64{9, 8, 2, 3, 11, 10},
  1687  			),
  1688  			Entry(
  1689  				"Filter by policy.apiGroup",
  1690  				[]string{"policy.apiGroup=policy.open-cluster-management.io"},
  1691  				[]float64{9, 8, 6, 7, 5, 4, 3, 2, 11, 10, 1},
  1692  			),
  1693  			Entry(
  1694  				"Filter by policy.apiGroup no results",
  1695  				[]string{"policy.apiGroup=does-not-exist"},
  1696  				[]float64{},
  1697  			),
  1698  			Entry(
  1699  				"Filter by policy.id",
  1700  				[]string{"policy.id=4"},
  1701  				[]float64{6, 5, 4},
  1702  			),
  1703  			Entry(
  1704  				"Filter by policy.kind",
  1705  				[]string{"policy.kind=ConfigurationPolicy"},
  1706  				[]float64{9, 8, 6, 7, 5, 4, 3, 2, 11, 10, 1},
  1707  			),
  1708  			Entry(
  1709  				"Filter by policy.kind no results",
  1710  				[]string{"policy.kind=something-else"},
  1711  				[]float64{},
  1712  			),
  1713  			Entry(
  1714  				"Filter by policy.name",
  1715  				[]string{"policy.name=common-b"},
  1716  				[]float64{11, 10},
  1717  			),
  1718  			Entry(
  1719  				"Filter by policy.namespace",
  1720  				[]string{"policy.namespace=default"},
  1721  				[]float64{10},
  1722  			),
  1723  			Entry(
  1724  				"Filter by policy.namespace is null",
  1725  				[]string{"policy.namespace"},
  1726  				[]float64{9, 8, 6, 7, 5, 4, 2, 3, 11},
  1727  			),
  1728  			Entry(
  1729  				"Filter by policy.severity",
  1730  				[]string{"policy.severity=low"},
  1731  				[]float64{9, 8, 6, 7, 5, 4, 11, 10, 1},
  1732  			),
  1733  			Entry(
  1734  				"Filter by policy.severity is null",
  1735  				[]string{"policy.severity"},
  1736  				[]float64{2, 3},
  1737  			),
  1738  		)
  1739  
  1740  		DescribeTable("Invalid API filtering",
  1741  			func(ctx context.Context, queryArgs []string, expectedErrMsg string) {
  1742  				_, err := listEvents(ctx, clientToken, queryArgs...)
  1743  				Expect(err).To(MatchError(ContainSubstring(expectedErrMsg)))
  1744  			},
  1745  			Entry(
  1746  				"Filter by empty event.timestamp_before is invalid",
  1747  				[]string{"event.timestamp_before"},
  1748  				"invalid query argument: event.timestamp_before must have a value",
  1749  			),
  1750  			Entry(
  1751  				"Filter by invalid event.timestamp_before",
  1752  				[]string{"event.timestamp_before=1993"},
  1753  				"invalid query argument: event.timestamp_before must be in the format of RFC 3339",
  1754  			),
  1755  			Entry(
  1756  				"Filter by invalid event.timestamp_after",
  1757  				[]string{"event.timestamp_after=1993"},
  1758  				"invalid query argument: event.timestamp_after must be in the format of RFC 3339",
  1759  			),
  1760  		)
  1761  
  1762  		Describe("Test the /api/v1/reports/compliance-events endpoint", func() {
  1763  			It("should send CSV file in http response", func(ctx context.Context) {
  1764  				req, err := http.NewRequestWithContext(ctx, http.MethodGet, csvEndpoint, nil)
  1765  				Expect(err).ShouldNot(HaveOccurred())
  1766  
  1767  				req.Header.Set("Authorization", "Bearer "+clientToken)
  1768  
  1769  				resp, err := httpClient.Do(req)
  1770  				Expect(err).ShouldNot(HaveOccurred())
  1771  
  1772  				defer resp.Body.Close()
  1773  
  1774  				By("Content-type should be CSV")
  1775  				Expect(resp.Header.Get("Content-Type")).Should(Equal("text/csv"))
  1776  
  1777  				csvReader := csv.NewReader(resp.Body)
  1778  
  1779  				records, err := csvReader.ReadAll()
  1780  				Expect(err).ShouldNot(HaveOccurred())
  1781  
  1782  				Expect(len(records)).Should(BeNumerically(">", 10))
  1783  
  1784  				By("First line should be the titles")
  1785  				Expect(records[0]).Should(ContainElements([]string{
  1786  					"compliance_events_id",
  1787  					"compliance_events_compliance",
  1788  					"compliance_events_message",
  1789  					"compliance_events_metadata",
  1790  					"compliance_events_reported_by",
  1791  					"compliance_events_timestamp",
  1792  					"clusters_cluster_id",
  1793  					"clusters_name",
  1794  					"parent_policies_id",
  1795  					"parent_policies_name",
  1796  					"parent_policies_namespace",
  1797  					"parent_policies_categories",
  1798  					"parent_policies_controls",
  1799  					"parent_policies_standards",
  1800  					"policies_id",
  1801  					"policies_api_group",
  1802  					"policies_kind",
  1803  					"policies_name",
  1804  					"policies_namespace",
  1805  					"policies_severity",
  1806  				}))
  1807  
  1808  				By("All line should have 20 columns")
  1809  				for _, r := range records {
  1810  					Expect(r).Should(HaveLen(20))
  1811  				}
  1812  			})
  1813  			It("Should return only header when SA does not have any GET verb to managedCluster",
  1814  				func(ctx context.Context) {
  1815  					req, err := http.NewRequestWithContext(ctx, http.MethodGet, csvEndpoint, nil)
  1816  					Expect(err).ShouldNot(HaveOccurred())
  1817  
  1818  					req.Header.Set("Content-Type", "application/json")
  1819  					// Set auth token
  1820  					req.Header.Set("Authorization", "Bearer "+wrongSAToken)
  1821  
  1822  					resp, err := httpClient.Do(req)
  1823  					Expect(err).ShouldNot(HaveOccurred())
  1824  
  1825  					defer resp.Body.Close()
  1826  
  1827  					By("Content-type should be CSV")
  1828  					Expect(resp.Header.Get("Content-Type")).Should(Equal("text/csv"))
  1829  
  1830  					csvReader := csv.NewReader(resp.Body)
  1831  
  1832  					records, err := csvReader.ReadAll()
  1833  					Expect(err).ShouldNot(HaveOccurred())
  1834  
  1835  					By("Should return only header")
  1836  					Expect(records).Should(HaveLen(1))
  1837  
  1838  					Expect(records[0]).Should(ContainElements([]string{
  1839  						"compliance_events_id",
  1840  						"compliance_events_compliance",
  1841  						"compliance_events_message",
  1842  						"compliance_events_metadata",
  1843  						"compliance_events_reported_by",
  1844  						"compliance_events_timestamp",
  1845  						"clusters_cluster_id",
  1846  						"clusters_name",
  1847  						"parent_policies_id",
  1848  						"parent_policies_name",
  1849  						"parent_policies_namespace",
  1850  						"parent_policies_categories",
  1851  						"parent_policies_controls",
  1852  						"parent_policies_standards",
  1853  						"policies_id",
  1854  						"policies_api_group",
  1855  						"policies_kind",
  1856  						"policies_name",
  1857  						"policies_namespace",
  1858  						"policies_severity",
  1859  					}))
  1860  				})
  1861  
  1862  			DescribeTable("Should filter CSV file",
  1863  				func(ctx context.Context, queryArgs []string, expectedLine int) {
  1864  					endpoints := csvEndpoint
  1865  
  1866  					endpoints += "?" + strings.Join(queryArgs, "&")
  1867  
  1868  					req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoints, nil)
  1869  					Expect(err).ShouldNot(HaveOccurred())
  1870  
  1871  					// Set auth token
  1872  					req.Header.Set("Authorization", "Bearer "+clientToken)
  1873  
  1874  					resp, err := httpClient.Do(req)
  1875  					Expect(err).ShouldNot(HaveOccurred())
  1876  
  1877  					defer resp.Body.Close()
  1878  
  1879  					csvReader := csv.NewReader(resp.Body)
  1880  					records, err := csvReader.ReadAll()
  1881  					Expect(err).ShouldNot(HaveOccurred())
  1882  
  1883  					// The first element is title
  1884  					Expect(records).Should(HaveLen(expectedLine))
  1885  				},
  1886  				Entry(
  1887  					"Filter by cluster.cluster_id",
  1888  					[]string{"cluster.cluster_id=test1-managed1-fake-uuid-1,test6-managed6-fake-uuid-6"},
  1889  					// titles + actual data
  1890  					4,
  1891  				),
  1892  				Entry(
  1893  					"Filter by cluster.name",
  1894  					[]string{"cluster.name=managed1,managed6"},
  1895  					4,
  1896  				),
  1897  				Entry(
  1898  					"Filter by event.compliance",
  1899  					[]string{"event.compliance=Compliant"},
  1900  					7,
  1901  				),
  1902  				Entry(
  1903  					"Filter by event.message",
  1904  					[]string{"event.message=configmaps%20%5Bcommon%5D%20not%20found%20in%20namespace%20default"},
  1905  					4,
  1906  				),
  1907  				Entry(
  1908  					"Filter by event.message_includes",
  1909  					[]string{"event.message_includes=etcd"},
  1910  					4,
  1911  				),
  1912  				Entry(
  1913  					"Filter by event.message_like",
  1914  					[]string{"event.message_like=configmaps%20%5B%25common%25%5D%25"},
  1915  					9,
  1916  				),
  1917  				Entry(
  1918  					"Filter by event.timestamp",
  1919  					[]string{"event.timestamp=2023-01-01T01:01:01.111Z"},
  1920  					2,
  1921  				),
  1922  				Entry(
  1923  					"Filter by event.timestamp_after",
  1924  					[]string{"event.timestamp_after=2023-04-01T01:01:01.111Z"},
  1925  					6,
  1926  				),
  1927  				Entry(
  1928  					"Filter by event.timestamp_before",
  1929  					[]string{"event.timestamp_before=2023-04-01T01:01:01.111Z"},
  1930  					7,
  1931  				),
  1932  				Entry(
  1933  					"Filter by event.timestamp_after and event.timestamp_before",
  1934  					[]string{
  1935  						"event.timestamp_after=2023-01-01T01:01:01.111Z",
  1936  						"event.timestamp_before=2023-04-01T01:01:01.111Z",
  1937  					},
  1938  					6,
  1939  				),
  1940  				Entry(
  1941  					"Filter by parent_policy.categories",
  1942  					[]string{"parent_policy.categories=cat-1,cat-3"},
  1943  					5,
  1944  				),
  1945  				Entry(
  1946  					"Filter by parent_policy.controls",
  1947  					[]string{"parent_policy.controls=ctrl-2"},
  1948  					4,
  1949  				),
  1950  				Entry(
  1951  					"Filter by parent_policy.id",
  1952  					[]string{"parent_policy.id=2"},
  1953  					4,
  1954  				),
  1955  				Entry(
  1956  					"Filter by parent_policy.name",
  1957  					[]string{"parent_policy.name=etcd-encryption1"},
  1958  					2,
  1959  				),
  1960  				Entry(
  1961  					"Filter by parent_policy.namespace",
  1962  					[]string{"parent_policy.namespace=policies"},
  1963  					10,
  1964  				),
  1965  				Entry(
  1966  					"Filter by parent_policy.standards",
  1967  					[]string{"parent_policy.standards=stand-2"},
  1968  					4,
  1969  				),
  1970  				Entry(
  1971  					"Filter by policy.apiGroup",
  1972  					[]string{"policy.apiGroup=policy.open-cluster-management.io"},
  1973  					12,
  1974  				),
  1975  				Entry(
  1976  					"Filter by policy.apiGroup no results",
  1977  					[]string{"policy.apiGroup=does-not-exist"},
  1978  					1,
  1979  				),
  1980  				Entry(
  1981  					"Filter by policy.id",
  1982  					[]string{"policy.id=4"},
  1983  					4,
  1984  				),
  1985  				Entry(
  1986  					"Filter by policy.kind",
  1987  					[]string{"policy.kind=ConfigurationPolicy"},
  1988  					12,
  1989  				),
  1990  				Entry(
  1991  					"Filter by policy.kind no results",
  1992  					[]string{"policy.kind=something-else"},
  1993  					1,
  1994  				),
  1995  				Entry(
  1996  					"Filter by policy.name",
  1997  					[]string{"policy.name=common-b"},
  1998  					3,
  1999  				),
  2000  				Entry(
  2001  					"Filter by policy.namespace",
  2002  					[]string{"policy.namespace=default"},
  2003  					2,
  2004  				),
  2005  				Entry(
  2006  					"Filter by policy.severity",
  2007  					[]string{"policy.severity=low"},
  2008  					10,
  2009  				),
  2010  				Entry(
  2011  					"Filter by policy.severity is null",
  2012  					[]string{"policy.severity"},
  2013  					3,
  2014  				),
  2015  			)
  2016  		})
  2017  	})
  2018  
  2019  	Describe("Duplicate compliance event", func() {
  2020  		payload1 := []byte(`{
  2021  			"cluster": {
  2022  				"name": "managed2",
  2023  				"cluster_id": "test2-managed2-fake-uuid-2"
  2024  			},
  2025  			"policy": {
  2026  				"apiGroup": "policy.open-cluster-management.io",
  2027  				"kind": "ConfigurationPolicy",
  2028  				"name": "duplicate-test",
  2029  				"spec": {"test": "two"}
  2030  			},
  2031  			"event": {
  2032  				"compliance": "NonCompliant",
  2033  				"message": "configmaps [etcd] not found in namespace default",
  2034  				"timestamp": "2023-02-02T02:02:02.222Z"
  2035  			}
  2036  		}`)
  2037  
  2038  		BeforeAll(func(ctx context.Context) {
  2039  			By("POST the initial event")
  2040  			Eventually(postEvent(ctx, payload1, clientToken), "5s", "1s").ShouldNot(HaveOccurred())
  2041  		})
  2042  
  2043  		It("Should fail when posting the same compliance event", func(ctx context.Context) {
  2044  			err := postEvent(ctx, payload1, clientToken)
  2045  			Expect(err).To(MatchError(ContainSubstring("The compliance event already exists")))
  2046  		})
  2047  	})
  2048  
  2049  	Describe("Test authorization", func() {
  2050  		Describe("Test method Get", func() {
  2051  			It("Should return unauthorized when it is empty token", func(ctx context.Context) {
  2052  				req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"/1", nil)
  2053  				Expect(err).ToNot(HaveOccurred())
  2054  
  2055  				res, err := httpClient.Do(req)
  2056  				Expect(res.StatusCode).Should(Equal(http.StatusUnauthorized))
  2057  				Expect(err).ShouldNot(HaveOccurred())
  2058  
  2059  				req, err = http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint, nil)
  2060  				Expect(err).ToNot(HaveOccurred())
  2061  
  2062  				res, err = httpClient.Do(req)
  2063  				Expect(res.StatusCode).Should(Equal(http.StatusUnauthorized))
  2064  				Expect(err).ShouldNot(HaveOccurred())
  2065  
  2066  				req, err = http.NewRequestWithContext(ctx, http.MethodGet, csvEndpoint, nil)
  2067  				Expect(err).ToNot(HaveOccurred())
  2068  
  2069  				res, err = httpClient.Do(req)
  2070  				Expect(res.StatusCode).Should(Equal(http.StatusUnauthorized))
  2071  				Expect(err).ShouldNot(HaveOccurred())
  2072  			})
  2073  			It("Should return empty data when SA does not have any GET verb to managedCluster",
  2074  				func(ctx context.Context) {
  2075  					req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint, nil)
  2076  					Expect(err).ShouldNot(HaveOccurred())
  2077  
  2078  					req.Header.Set("Content-Type", "application/json")
  2079  					// Set auth token
  2080  					req.Header.Set("Authorization", "Bearer "+wrongSAToken)
  2081  
  2082  					resp, err := httpClient.Do(req)
  2083  					Expect(err).ShouldNot(HaveOccurred())
  2084  
  2085  					defer resp.Body.Close()
  2086  
  2087  					body, err := io.ReadAll(resp.Body)
  2088  					Expect(err).ShouldNot(HaveOccurred())
  2089  
  2090  					respJSON := map[string]any{}
  2091  
  2092  					err = json.Unmarshal(body, &respJSON)
  2093  					Expect(err).ShouldNot(HaveOccurred())
  2094  
  2095  					rows, ok := respJSON["data"].([]interface{})
  2096  					Expect(ok).To(BeTrue())
  2097  
  2098  					By("Should return 0 rows")
  2099  					Expect(rows).Should(BeEmpty())
  2100  				})
  2101  
  2102  			It("Should return empty data when only unknown cluster IDs are provided",
  2103  				func(ctx context.Context) {
  2104  					endpoint := eventsEndpoint + "?cluster.cluster_id=does-not-exist,does-also-not-exist"
  2105  					req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
  2106  					Expect(err).ShouldNot(HaveOccurred())
  2107  					req.Header.Set("Authorization", "Bearer "+clientToken)
  2108  
  2109  					resp, err := httpClient.Do(req)
  2110  					Expect(err).ShouldNot(HaveOccurred())
  2111  
  2112  					defer resp.Body.Close()
  2113  
  2114  					body, err := io.ReadAll(resp.Body)
  2115  					Expect(err).ShouldNot(HaveOccurred())
  2116  
  2117  					respJSON := map[string]any{}
  2118  
  2119  					err = json.Unmarshal(body, &respJSON)
  2120  					Expect(err).ShouldNot(HaveOccurred())
  2121  
  2122  					rows, ok := respJSON["data"].([]interface{})
  2123  					Expect(ok).To(BeTrue())
  2124  
  2125  					By("Should return 0 rows")
  2126  					Expect(rows).Should(BeEmpty())
  2127  				},
  2128  			)
  2129  
  2130  			It("Should return a forbidden error when SA has only managed1 auth",
  2131  				func(ctx context.Context) {
  2132  					argument := "cluster.name=managed1,managed2,managed3"
  2133  
  2134  					By("governance-policy-propagator SA Should be able to access all")
  2135  
  2136  					respJSON, err := listEvents(ctx, clientToken, argument)
  2137  					Expect(err).ShouldNot(HaveOccurred())
  2138  
  2139  					data, ok := respJSON["data"].([]any)
  2140  					Expect(ok).Should(BeTrue())
  2141  
  2142  					By("Should include at least managed2 or managed3")
  2143  					hasVariousClusters := false
  2144  					for _, d := range data {
  2145  						complianceEvent, ok := d.(map[string]interface{})
  2146  						Expect(ok).To(BeTrue())
  2147  
  2148  						name, ok := complianceEvent["cluster"].(map[string]interface{})["name"].(string)
  2149  						Expect(ok).To(BeTrue())
  2150  
  2151  						if name == "managed2" || name == "managed3" {
  2152  							hasVariousClusters = true
  2153  
  2154  							break
  2155  						}
  2156  					}
  2157  					Expect(hasVariousClusters).Should(BeTrue())
  2158  
  2159  					req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"?"+argument, nil)
  2160  					Expect(err).ShouldNot(HaveOccurred())
  2161  
  2162  					req.Header.Set("Content-Type", "application/json")
  2163  					// Set auth token
  2164  					req.Header.Set("Authorization", "Bearer "+subsetSAToken)
  2165  
  2166  					resp, err := httpClient.Do(req)
  2167  					Expect(err).ShouldNot(HaveOccurred())
  2168  
  2169  					defer resp.Body.Close()
  2170  
  2171  					body, err := io.ReadAll(resp.Body)
  2172  					Expect(err).ShouldNot(HaveOccurred())
  2173  
  2174  					respJSON = map[string]any{}
  2175  
  2176  					err = json.Unmarshal(body, &respJSON)
  2177  					Expect(err).ShouldNot(HaveOccurred())
  2178  
  2179  					message, ok := respJSON["message"].(string)
  2180  					Expect(ok).To(BeTrue())
  2181  
  2182  					Expect(message).
  2183  						Should(Equal("the request is not allowed: the following cluster filters are not authorized: " +
  2184  							"managed2, managed3"))
  2185  
  2186  					Expect(resp.StatusCode).Should(Equal(http.StatusForbidden))
  2187  					Expect(err).ShouldNot(HaveOccurred())
  2188  				})
  2189  			It("Should return a forbidden error when only unauthorized ID are passed as id",
  2190  				func(ctx context.Context) {
  2191  					argument := "cluster.cluster_id=wrong-id,test1-managed1-fake-uuid-1,test2-managed2-fake-uuid-2"
  2192  
  2193  					req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint+"?"+argument, nil)
  2194  					Expect(err).ShouldNot(HaveOccurred())
  2195  
  2196  					req.Header.Set("Content-Type", "application/json")
  2197  					// Set auth token
  2198  					req.Header.Set("Authorization", "Bearer "+subsetSAToken)
  2199  
  2200  					resp, err := httpClient.Do(req)
  2201  					Expect(err).ShouldNot(HaveOccurred())
  2202  
  2203  					defer resp.Body.Close()
  2204  
  2205  					body, err := io.ReadAll(resp.Body)
  2206  					Expect(err).ShouldNot(HaveOccurred())
  2207  
  2208  					respJSON := map[string]any{}
  2209  
  2210  					err = json.Unmarshal(body, &respJSON)
  2211  					Expect(err).ShouldNot(HaveOccurred())
  2212  
  2213  					message, ok := respJSON["message"].(string)
  2214  					Expect(ok).To(BeTrue())
  2215  
  2216  					By("The error message should include test2-managed2-fake-uuid-2 except managed1")
  2217  					Expect(message).
  2218  						Should(Equal(
  2219  							"the request is not allowed: the following cluster filters are not authorized: " +
  2220  								"test2-managed2-fake-uuid-2"))
  2221  
  2222  					Expect(resp.StatusCode).Should(Equal(http.StatusForbidden))
  2223  					Expect(err).ShouldNot(HaveOccurred())
  2224  				})
  2225  			It("Should return managed1 with subset SA when the query is empty",
  2226  				func(ctx context.Context) {
  2227  					req, err := http.NewRequestWithContext(ctx, http.MethodGet, eventsEndpoint, nil)
  2228  					Expect(err).ShouldNot(HaveOccurred())
  2229  
  2230  					req.Header.Set("Content-Type", "application/json")
  2231  					// Set auth token
  2232  					req.Header.Set("Authorization", "Bearer "+subsetSAToken)
  2233  
  2234  					resp, err := httpClient.Do(req)
  2235  					Expect(err).ShouldNot(HaveOccurred())
  2236  
  2237  					defer resp.Body.Close()
  2238  
  2239  					body, err := io.ReadAll(resp.Body)
  2240  					Expect(err).ShouldNot(HaveOccurred())
  2241  
  2242  					respJSON := map[string]any{}
  2243  
  2244  					err = json.Unmarshal(body, &respJSON)
  2245  					Expect(err).ShouldNot(HaveOccurred())
  2246  
  2247  					rows, ok := respJSON["data"].([]interface{})
  2248  					Expect(ok).To(BeTrue())
  2249  
  2250  					By("Should return only managed1")
  2251  					Expect(rows).Should(HaveLen(1))
  2252  
  2253  					id, ok := rows[0].(map[string]interface{})["id"].(float64)
  2254  					Expect(ok).To(BeTrue())
  2255  
  2256  					Expect(int(id)).Should(Equal(1))
  2257  				})
  2258  		})
  2259  	})
  2260  })
  2261  
  2262  var _ = Describe("Test query generation", Label("compliance-events-api"), func() {
  2263  	It("Tests the select query for a cluster", func() {
  2264  		cluster := complianceeventsapi.Cluster{
  2265  			ClusterID: "my-cluster-id",
  2266  			Name:      "my-cluster",
  2267  		}
  2268  		sql, vals := cluster.SelectQuery("id", "spec")
  2269  		Expect(sql).To(Equal("SELECT id, spec FROM clusters WHERE cluster_id=$1 AND name=$2"))
  2270  		Expect(vals).To(HaveLen(2))
  2271  	})
  2272  
  2273  	It("Tests the select query for a minimum parent policy", func() {
  2274  		parent := complianceeventsapi.ParentPolicy{
  2275  			Name:      "parent-a",
  2276  			Namespace: "policies",
  2277  		}
  2278  		sql, vals := parent.SelectQuery("id", "spec")
  2279  		Expect(sql).To(Equal(
  2280  			"SELECT id, spec FROM parent_policies WHERE name=$1 AND namespace=$2 AND categories IS NULL AND " +
  2281  				"controls IS NULL AND standards IS NULL",
  2282  		))
  2283  		Expect(vals).To(HaveLen(2))
  2284  	})
  2285  
  2286  	It("Tests the select query for a parent policy with all options", func() {
  2287  		parent := complianceeventsapi.ParentPolicy{
  2288  			Name:       "parent-a",
  2289  			Namespace:  "policies",
  2290  			Categories: pq.StringArray{"cat-1"},
  2291  			Controls:   pq.StringArray{"control-1", "control-2"},
  2292  			Standards:  pq.StringArray{"standard-1"},
  2293  		}
  2294  		sql, vals := parent.SelectQuery("id")
  2295  		Expect(sql).To(Equal(
  2296  			"SELECT id FROM parent_policies WHERE name=$1 AND namespace=$2 AND categories=$3 AND controls=$4 " +
  2297  				"AND standards=$5",
  2298  		))
  2299  		Expect(vals).To(HaveLen(5))
  2300  	})
  2301  
  2302  	It("Tests the select query for a minimum policy", func() {
  2303  		policy := complianceeventsapi.Policy{
  2304  			Name:     "parent-a",
  2305  			Kind:     "ConfigurationPolicy",
  2306  			APIGroup: "policy.open-cluster-management.io",
  2307  			Spec:     complianceeventsapi.JSONMap{"spec": "this-out"},
  2308  		}
  2309  		sql, vals := policy.SelectQuery("id")
  2310  		Expect(sql).To(Equal(
  2311  			"SELECT id FROM policies WHERE api_group=$1 AND kind=$2 AND name=$3 AND spec=$4 AND namespace is NULL " +
  2312  				"AND severity is NULL",
  2313  		))
  2314  		Expect(vals).To(HaveLen(4))
  2315  	})
  2316  
  2317  	It("Tests the select query for a policy with all options", func() {
  2318  		ns := "policies"
  2319  		severity := "critical"
  2320  
  2321  		policy := complianceeventsapi.Policy{
  2322  			Name:      "parent-a",
  2323  			Namespace: &ns,
  2324  			Kind:      "ConfigurationPolicy",
  2325  			APIGroup:  "policy.open-cluster-management.io",
  2326  			Spec:      complianceeventsapi.JSONMap{"spec": "this-out"},
  2327  			Severity:  &severity,
  2328  		}
  2329  		sql, vals := policy.SelectQuery("id")
  2330  		Expect(sql).To(Equal(
  2331  			"SELECT id FROM policies WHERE api_group=$1 AND kind=$2 AND name=$3 AND spec=$4 AND namespace=$5 " +
  2332  				"AND severity=$6",
  2333  		))
  2334  		Expect(vals).To(HaveLen(6))
  2335  	})
  2336  })
  2337  
  2338  func postEvent(ctx context.Context, payload []byte, token string) error {
  2339  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, eventsEndpoint, bytes.NewBuffer(payload))
  2340  	if err != nil {
  2341  		return err
  2342  	}
  2343  
  2344  	req.Header.Set("Content-Type", "application/json")
  2345  	req.Header.Set("Authorization", "Bearer "+token)
  2346  
  2347  	errs := make([]error, 0)
  2348  
  2349  	resp, err := httpClient.Do(req)
  2350  	if err != nil {
  2351  		errs = append(errs, err)
  2352  	}
  2353  
  2354  	if resp != nil {
  2355  		defer resp.Body.Close()
  2356  
  2357  		body, err := io.ReadAll(resp.Body)
  2358  		if err != nil {
  2359  			errs = append(errs, err)
  2360  		}
  2361  
  2362  		if resp.StatusCode != http.StatusCreated {
  2363  			errs = append(errs, fmt.Errorf("Got non-201 status code %v; response: %q", resp.StatusCode, string(body)))
  2364  		}
  2365  	}
  2366  
  2367  	return errors.Join(errs...)
  2368  }
  2369  
  2370  func listEvents(ctx context.Context, token string, queryArgs ...string) (map[string]any, error) {
  2371  	url := eventsEndpoint
  2372  
  2373  	if len(queryArgs) > 0 {
  2374  		url += "?" + strings.Join(queryArgs, "&")
  2375  	}
  2376  
  2377  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
  2378  	if err != nil {
  2379  		return nil, err
  2380  	}
  2381  
  2382  	// Set auth token
  2383  	req.Header.Set("Authorization", "Bearer "+token)
  2384  
  2385  	resp, err := httpClient.Do(req)
  2386  	if err != nil {
  2387  		return nil, err
  2388  	}
  2389  
  2390  	defer resp.Body.Close()
  2391  
  2392  	body, err := io.ReadAll(resp.Body)
  2393  	if err != nil {
  2394  		return nil, err
  2395  	}
  2396  
  2397  	respJSON := map[string]any{}
  2398  
  2399  	err = json.Unmarshal(body, &respJSON)
  2400  	if err != nil {
  2401  		return nil, err
  2402  	}
  2403  
  2404  	if resp.StatusCode != http.StatusOK {
  2405  		return respJSON, fmt.Errorf("Got non-200 status code %v; response: %q", resp.StatusCode, string(body))
  2406  	}
  2407  
  2408  	return respJSON, nil
  2409  }
  2410  
  2411  func getToken(ctx context.Context, ns, saName string) string {
  2412  	secret := &v1.Secret{}
  2413  	var err error
  2414  
  2415  	Eventually(func(g Gomega) error {
  2416  		secret, err = clientHub.CoreV1().Secrets(ns).
  2417  			Get(ctx, saName, metav1.GetOptions{})
  2418  
  2419  		_, ok := secret.Data["token"]
  2420  		g.Expect(ok).Should(BeTrue())
  2421  
  2422  		return err
  2423  	}).ShouldNot(HaveOccurred())
  2424  
  2425  	_, ok := secret.Data["token"]
  2426  	Expect(ok).Should(BeTrue())
  2427  
  2428  	return string(secret.Data["token"])
  2429  }