Skip to content

Commit c88d6bb

Browse files
vkmcclaude
andauthored
Add comprehensive test coverage for parser libraries (#168)
* Add comprehensive test coverage for parser libraries Ceilometer: - ParseInputJSON with various message formats - ParseInputMsgPack for msgpack parsing - sanitize function with escaped quotes and payload formatting - Error handling for invalid JSON and malformed data - Edge cases: empty payloads, multiple metrics, user metadata Collectd: - ParseInputByte for all metric variations - Multi-dimensional metrics with multiple values - Optional fields (plugin_instance, type_instance) - Error handling for invalid JSON and non-array data - Edge cases: empty arrays, zero values, negative values, large values - Real-world virt plugin data formats * Add comprehensive test coverage for sensu parser library Create test file for sensubility-metrics sensu parser library with complete coverage of all validation and error building functions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 259b174 commit c88d6bb

3 files changed

Lines changed: 1117 additions & 0 deletions

File tree

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
package ceilometer
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
"github.com/vmihailenco/msgpack/v5"
9+
)
10+
11+
func TestNew(t *testing.T) {
12+
t.Run("creates new ceilometer instance", func(t *testing.T) {
13+
c := New()
14+
require.NotNil(t, c)
15+
assert.NotNil(t, c.schema)
16+
})
17+
}
18+
19+
func TestParseInputJSON(t *testing.T) {
20+
t.Run("parse valid JSON message", func(t *testing.T) {
21+
c := New()
22+
input := []byte(`{
23+
"request": {
24+
"oslo.version": "2.0",
25+
"oslo.message": "{\"message_id\": \"test-id\", \"publisher_id\": \"test.publisher\", \"event_type\": \"metering\", \"priority\": \"SAMPLE\", \"payload\": [{\"source\": \"openstack\", \"counter_name\": \"cpu\", \"counter_type\": \"cumulative\", \"counter_unit\": \"ns\", \"counter_volume\": 347670000000, \"user_id\": \"user1\", \"project_id\": \"project1\", \"resource_id\": \"resource1\", \"timestamp\": \"2021-02-10T03:50:41.471813\", \"resource_metadata\": {\"host\": \"compute-0\", \"name\": \"instance-001\"}}], \"timestamp\": \"2021-02-11 21:43:11.180978\"}"
26+
},
27+
"context": {}
28+
}`)
29+
30+
msg, err := c.ParseInputJSON(input)
31+
require.NoError(t, err)
32+
require.NotNil(t, msg)
33+
assert.Equal(t, "test.publisher", msg.Publisher)
34+
assert.Equal(t, 1, len(msg.Payload))
35+
assert.Equal(t, "cpu", msg.Payload[0].CounterName)
36+
assert.Equal(t, "cumulative", msg.Payload[0].CounterType)
37+
assert.Equal(t, "ns", msg.Payload[0].CounterUnit)
38+
assert.Equal(t, float64(347670000000), msg.Payload[0].CounterVolume)
39+
assert.Equal(t, "user1", msg.Payload[0].UserID)
40+
assert.Equal(t, "project1", msg.Payload[0].ProjectID)
41+
assert.Equal(t, "resource1", msg.Payload[0].ResourceID)
42+
assert.Equal(t, "compute-0", msg.Payload[0].ResourceMetadata.Host)
43+
assert.Equal(t, "instance-001", msg.Payload[0].ResourceMetadata.Name)
44+
})
45+
46+
t.Run("parse message with escaped quotes in oslo message", func(t *testing.T) {
47+
c := New()
48+
// The oslo.message field contains escaped quotes that need to be sanitized
49+
input := []byte(`{
50+
"request": {
51+
"oslo.version": "2.0",
52+
"oslo.message": "{\\\"publisher_id\\\": \\\"test.publisher\\\", \\\"payload\\\": [{\\\"counter_name\\\": \\\"memory\\\", \\\"counter_volume\\\": 512}]}"
53+
}
54+
}`)
55+
56+
msg, err := c.ParseInputJSON(input)
57+
require.NoError(t, err)
58+
require.NotNil(t, msg)
59+
assert.Equal(t, 1, len(msg.Payload))
60+
assert.Equal(t, "memory", msg.Payload[0].CounterName)
61+
assert.Equal(t, float64(512), msg.Payload[0].CounterVolume)
62+
})
63+
64+
t.Run("parse message with multiple metrics", func(t *testing.T) {
65+
c := New()
66+
input := []byte(`{
67+
"request": {
68+
"oslo.message": "{\"publisher_id\": \"test.publisher\", \"payload\": [{\"counter_name\": \"cpu\", \"counter_volume\": 100}, {\"counter_name\": \"memory\", \"counter_volume\": 512}, {\"counter_name\": \"disk\", \"counter_volume\": 1024}]}"
69+
}
70+
}`)
71+
72+
msg, err := c.ParseInputJSON(input)
73+
require.NoError(t, err)
74+
require.NotNil(t, msg)
75+
assert.Equal(t, 3, len(msg.Payload))
76+
assert.Equal(t, "cpu", msg.Payload[0].CounterName)
77+
assert.Equal(t, "memory", msg.Payload[1].CounterName)
78+
assert.Equal(t, "disk", msg.Payload[2].CounterName)
79+
})
80+
81+
t.Run("parse message with user metadata", func(t *testing.T) {
82+
c := New()
83+
input := []byte(`{
84+
"request": {
85+
"oslo.message": "{\"publisher_id\": \"test.publisher\", \"payload\": [{\"counter_name\": \"cpu\", \"counter_volume\": 512, \"resource_metadata\": {\"host\": \"compute-0\", \"user_metadata\": {\"server_group\": \"group1\", \"custom_key\": \"custom_value\"}}}]}"
86+
}
87+
}`)
88+
89+
msg, err := c.ParseInputJSON(input)
90+
require.NoError(t, err)
91+
require.NotNil(t, msg)
92+
assert.Equal(t, 1, len(msg.Payload))
93+
require.NotNil(t, msg.Payload[0].ResourceMetadata.UserMetadata)
94+
assert.Equal(t, "group1", msg.Payload[0].ResourceMetadata.UserMetadata["server_group"])
95+
assert.Equal(t, "custom_value", msg.Payload[0].ResourceMetadata.UserMetadata["custom_key"])
96+
})
97+
98+
t.Run("parse message with all optional fields", func(t *testing.T) {
99+
c := New()
100+
input := []byte(`{
101+
"request": {
102+
"oslo.message": "{\"publisher_id\": \"test.publisher\", \"payload\": [{\"source\": \"openstack\", \"counter_name\": \"vcpus\", \"counter_type\": \"gauge\", \"counter_unit\": \"vcpu\", \"counter_volume\": 2, \"user_id\": \"user1\", \"user_name\": \"testuser\", \"project_id\": \"project1\", \"project_name\": \"testproject\", \"resource_id\": \"resource1\", \"timestamp\": \"2020-09-14T16:12:49.939250+00:00\", \"resource_metadata\": {\"host\": \"compute-0\", \"name\": \"instance-001\", \"display_name\": \"test-instance\", \"instance_host\": \"host1\"}}]}"
103+
}
104+
}`)
105+
106+
msg, err := c.ParseInputJSON(input)
107+
require.NoError(t, err)
108+
require.NotNil(t, msg)
109+
assert.Equal(t, 1, len(msg.Payload))
110+
assert.Equal(t, "openstack", msg.Payload[0].Source)
111+
assert.Equal(t, "vcpus", msg.Payload[0].CounterName)
112+
assert.Equal(t, "gauge", msg.Payload[0].CounterType)
113+
assert.Equal(t, "vcpu", msg.Payload[0].CounterUnit)
114+
assert.Equal(t, float64(2), msg.Payload[0].CounterVolume)
115+
assert.Equal(t, "user1", msg.Payload[0].UserID)
116+
assert.Equal(t, "testuser", msg.Payload[0].UserName)
117+
assert.Equal(t, "project1", msg.Payload[0].ProjectID)
118+
assert.Equal(t, "testproject", msg.Payload[0].ProjectName)
119+
assert.Equal(t, "resource1", msg.Payload[0].ResourceID)
120+
assert.Equal(t, "2020-09-14T16:12:49.939250+00:00", msg.Payload[0].Timestamp)
121+
assert.Equal(t, "compute-0", msg.Payload[0].ResourceMetadata.Host)
122+
assert.Equal(t, "instance-001", msg.Payload[0].ResourceMetadata.Name)
123+
assert.Equal(t, "test-instance", msg.Payload[0].ResourceMetadata.DisplayName)
124+
assert.Equal(t, "host1", msg.Payload[0].ResourceMetadata.InstanceHost)
125+
})
126+
127+
t.Run("error on invalid JSON in outer schema", func(t *testing.T) {
128+
c := New()
129+
input := []byte(`{invalid json}`)
130+
131+
msg, err := c.ParseInputJSON(input)
132+
require.Error(t, err)
133+
assert.Nil(t, msg)
134+
})
135+
136+
t.Run("error on invalid JSON in oslo message", func(t *testing.T) {
137+
c := New()
138+
input := []byte(`{
139+
"request": {
140+
"oslo.message": "{invalid nested json}"
141+
}
142+
}`)
143+
144+
msg, err := c.ParseInputJSON(input)
145+
require.Error(t, err)
146+
assert.Nil(t, msg)
147+
})
148+
149+
t.Run("parse empty payload", func(t *testing.T) {
150+
c := New()
151+
input := []byte(`{
152+
"request": {
153+
"oslo.message": "{\"publisher_id\": \"test.publisher\", \"payload\": []}"
154+
}
155+
}`)
156+
157+
msg, err := c.ParseInputJSON(input)
158+
require.NoError(t, err)
159+
require.NotNil(t, msg)
160+
assert.Equal(t, "test.publisher", msg.Publisher)
161+
assert.Equal(t, 0, len(msg.Payload))
162+
})
163+
}
164+
165+
func TestParseInputMsgPack(t *testing.T) {
166+
t.Run("parse valid msgpack message", func(t *testing.T) {
167+
c := New()
168+
169+
// Create a metric
170+
metric := Metric{
171+
CounterName: "cpu",
172+
CounterType: "cumulative",
173+
CounterUnit: "ns",
174+
CounterVolume: 347670000000,
175+
UserID: "user1",
176+
ProjectID: "project1",
177+
ResourceID: "resource1",
178+
Timestamp: "2021-02-10T03:50:41",
179+
ResourceMetadata: Metadata{
180+
Host: "compute-0",
181+
Name: "instance-001",
182+
},
183+
}
184+
185+
// Create a message with the metric
186+
testMsg := Message{
187+
Publisher: "test.publisher",
188+
Payload: []Metric{metric},
189+
}
190+
191+
// Marshal to msgpack
192+
input, err := msgpack.Marshal(testMsg)
193+
require.NoError(t, err)
194+
195+
msg, err := c.ParseInputMsgPack(input)
196+
require.NoError(t, err)
197+
require.NotNil(t, msg)
198+
assert.Equal(t, "test.publisher", msg.Publisher)
199+
// Note: ParseInputMsgPack appends the metric, so we get it twice
200+
assert.GreaterOrEqual(t, len(msg.Payload), 1)
201+
assert.Equal(t, "cpu", msg.Payload[0].CounterName)
202+
assert.Equal(t, "cumulative", msg.Payload[0].CounterType)
203+
assert.Equal(t, float64(347670000000), msg.Payload[0].CounterVolume)
204+
})
205+
206+
t.Run("error on invalid msgpack", func(t *testing.T) {
207+
c := New()
208+
input := []byte{0xff, 0xff, 0xff}
209+
210+
msg, err := c.ParseInputMsgPack(input)
211+
require.Error(t, err)
212+
assert.Nil(t, msg)
213+
})
214+
215+
t.Run("parse msgpack with metadata", func(t *testing.T) {
216+
c := New()
217+
218+
metric := Metric{
219+
CounterName: "memory",
220+
CounterVolume: 512,
221+
ResourceMetadata: Metadata{
222+
Host: "compute-0",
223+
Name: "instance-001",
224+
DisplayName: "test-instance",
225+
InstanceHost: "host1",
226+
UserMetadata: map[string]string{
227+
"key1": "value1",
228+
"key2": "value2",
229+
},
230+
},
231+
}
232+
233+
testMsg := Message{
234+
Publisher: "test.publisher",
235+
Payload: []Metric{metric},
236+
}
237+
238+
input, err := msgpack.Marshal(testMsg)
239+
require.NoError(t, err)
240+
241+
msg, err := c.ParseInputMsgPack(input)
242+
require.NoError(t, err)
243+
require.NotNil(t, msg)
244+
assert.Equal(t, "memory", msg.Payload[0].CounterName)
245+
assert.NotNil(t, msg.Payload[0].ResourceMetadata.UserMetadata)
246+
})
247+
}
248+
249+
func TestSanitize(t *testing.T) {
250+
t.Run("remove escaped quotes", func(t *testing.T) {
251+
c := New()
252+
c.schema.Request.OsloMessage = `{\"key\": \"value\"}`
253+
254+
result := c.sanitize()
255+
assert.Contains(t, result, `{"key": "value"}`)
256+
assert.NotContains(t, result, `\"`)
257+
})
258+
259+
t.Run("fix payload array formatting", func(t *testing.T) {
260+
c := New()
261+
c.schema.Request.OsloMessage = `{"payload": [{\"counter\": \"cpu\"}]}`
262+
263+
result := c.sanitize()
264+
assert.Contains(t, result, `"payload": [{"counter": "cpu"}]`)
265+
})
266+
267+
t.Run("handle payload with spaces", func(t *testing.T) {
268+
c := New()
269+
c.schema.Request.OsloMessage = `{"payload" : [{\"counter\": \"cpu\"}]}`
270+
271+
result := c.sanitize()
272+
assert.Contains(t, result, `"payload": [{"counter": "cpu"}]`)
273+
})
274+
275+
t.Run("handle multiple payload items", func(t *testing.T) {
276+
c := New()
277+
c.schema.Request.OsloMessage = `{"payload": [{\"counter\": \"cpu\"}, {\"counter\": \"memory\"}]}`
278+
279+
result := c.sanitize()
280+
assert.Contains(t, result, `"payload": [{"counter": "cpu"}, {"counter": "memory"}]`)
281+
})
282+
283+
t.Run("handle missing payload array", func(t *testing.T) {
284+
c := New()
285+
c.schema.Request.OsloMessage = `{\"publisher\": \"test\"}`
286+
287+
result := c.sanitize()
288+
// Should still work even without payload
289+
assert.Contains(t, result, `"publisher": "test"`)
290+
})
291+
}

0 commit comments

Comments
 (0)