|
| 1 | +// Copyright 2026 EMQ Technologies Co., Ltd. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +package fvt |
| 16 | + |
| 17 | +import ( |
| 18 | + "bufio" |
| 19 | + "context" |
| 20 | + "encoding/json" |
| 21 | + "fmt" |
| 22 | + "net/http" |
| 23 | + "strings" |
| 24 | + "testing" |
| 25 | + "time" |
| 26 | + |
| 27 | + "github.com/stretchr/testify/suite" |
| 28 | +) |
| 29 | + |
| 30 | +type RuletestTestSuite struct { |
| 31 | + suite.Suite |
| 32 | +} |
| 33 | + |
| 34 | +func TestRuletestSuite(t *testing.T) { |
| 35 | + suite.Run(t, new(RuletestTestSuite)) |
| 36 | +} |
| 37 | + |
| 38 | +func (s *RuletestTestSuite) TestRuletestMockSourceUnnestKeepProjectedFields() { |
| 39 | + streamName := "demoRuletest5501" |
| 40 | + ruleID := "rule_ruletest_5501" |
| 41 | + |
| 42 | + _, _ = client.DeleteStream(streamName) |
| 43 | + _, _ = client.Delete(fmt.Sprintf("ruletest/%s", ruleID)) |
| 44 | + |
| 45 | + s.T().Cleanup(func() { |
| 46 | + _, _ = client.Delete(fmt.Sprintf("ruletest/%s", ruleID)) |
| 47 | + _, _ = client.DeleteStream(streamName) |
| 48 | + }) |
| 49 | + |
| 50 | + streamSQL := fmt.Sprintf(`{"sql":"CREATE STREAM %s (id STRING, time STRING, type STRING, data ARRAY(STRUCT(k BIGINT))) WITH (DATASOURCE=\"%s\", FORMAT=\"json\", TYPE=\"mqtt\")"}`, streamName, streamName) |
| 51 | + resp, err := client.CreateStream(streamSQL) |
| 52 | + s.Require().NoError(err) |
| 53 | + s.Require().Equal(http.StatusCreated, resp.StatusCode) |
| 54 | + |
| 55 | + ruleDef := fmt.Sprintf(`{ |
| 56 | + "id": "%s", |
| 57 | + "sql": "SELECT id, time, type, unnest(data) FROM %s", |
| 58 | + "mockSource": { |
| 59 | + "%s": { |
| 60 | + "loop": false, |
| 61 | + "data": [ |
| 62 | + { |
| 63 | + "id": "id1", |
| 64 | + "time": "2023-05-30T15:23:23.123+08:00", |
| 65 | + "type": "1", |
| 66 | + "data": [ |
| 67 | + {"k": 1}, |
| 68 | + {"k": 2} |
| 69 | + ] |
| 70 | + } |
| 71 | + ] |
| 72 | + } |
| 73 | + }, |
| 74 | + "sinkProps": { |
| 75 | + "sendSingle": true |
| 76 | + } |
| 77 | +}`, ruleID, streamName, streamName) |
| 78 | + |
| 79 | + resp, err = client.Post("ruletest", ruleDef) |
| 80 | + s.Require().NoError(err) |
| 81 | + s.Require().Equal(http.StatusOK, resp.StatusCode) |
| 82 | + |
| 83 | + result, err := GetResponseResultMap(resp) |
| 84 | + s.Require().NoError(err) |
| 85 | + s.Require().Equal(ruleID, result["id"]) |
| 86 | + port, ok := result["port"].(float64) |
| 87 | + s.Require().True(ok) |
| 88 | + |
| 89 | + sseURL := fmt.Sprintf("http://127.0.0.1:%d/test/%s", int(port), ruleID) |
| 90 | + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
| 91 | + defer cancel() |
| 92 | + |
| 93 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, sseURL, nil) |
| 94 | + s.Require().NoError(err) |
| 95 | + req.Header.Set("Accept", "text/event-stream") |
| 96 | + sseResp, err := http.DefaultClient.Do(req) |
| 97 | + s.Require().NoError(err) |
| 98 | + s.Require().Equal(http.StatusOK, sseResp.StatusCode) |
| 99 | + defer sseResp.Body.Close() |
| 100 | + |
| 101 | + // Start rule after SSE connected to avoid missing data. |
| 102 | + resp, err = client.Post(fmt.Sprintf("ruletest/%s/start", ruleID), "any") |
| 103 | + s.Require().NoError(err) |
| 104 | + s.Require().Equal(http.StatusOK, resp.StatusCode) |
| 105 | + |
| 106 | + scanner := bufio.NewScanner(sseResp.Body) |
| 107 | + var got map[string]any |
| 108 | + for scanner.Scan() { |
| 109 | + line := scanner.Text() |
| 110 | + if !strings.HasPrefix(line, "data: ") { |
| 111 | + continue |
| 112 | + } |
| 113 | + payload := strings.TrimPrefix(line, "data: ") |
| 114 | + s.T().Log(payload) |
| 115 | + s.Require().NoError(json.Unmarshal([]byte(payload), &got)) |
| 116 | + break |
| 117 | + } |
| 118 | + s.Require().NoError(scanner.Err()) |
| 119 | + s.Require().NotEmpty(got) |
| 120 | + |
| 121 | + // Regression guard: in mockSource+unnest ruletest, projected fields should not be dropped. |
| 122 | + s.Require().Equal("id1", got["id"]) |
| 123 | + s.Require().Equal("2023-05-30T15:23:23.123+08:00", got["time"]) |
| 124 | + s.Require().Equal("1", got["type"]) |
| 125 | + _, hasK := got["k"] |
| 126 | + s.Require().True(hasK) |
| 127 | +} |
0 commit comments