-
Notifications
You must be signed in to change notification settings - Fork 61
/
partition_message_test.go
197 lines (158 loc) · 6.2 KB
/
partition_message_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package util
import (
"strconv"
"strings"
"github.com/containrrr/shoutrrr/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Partition Message", func() {
limits := types.MessageLimit{
ChunkSize: 2000,
TotalChunkSize: 6000,
ChunkCount: 10,
}
When("given a message that exceeds the max length", func() {
When("not splitting by lines", func() {
It("should return a payload with chunked messages", func() {
items, _ := testPartitionMessage(42, limits, 100)
Expect(len(items[0].Text)).To(Equal(1994))
Expect(len(items[1].Text)).To(Equal(1999))
Expect(len(items[2].Text)).To(Equal(205))
})
It("omit characters above total max", func() {
items, _ := testPartitionMessage(62, limits, 100)
Expect(len(items[0].Text)).To(Equal(1994))
Expect(len(items[1].Text)).To(Equal(1999))
Expect(len(items[2].Text)).To(Equal(1999))
Expect(len(items[3].Text)).To(Equal(5))
})
It("should handle messages with a size modulus of chunksize", func() {
items, _ := testPartitionMessage(20, limits, 100)
// Last word fits in the chunk size
Expect(len(items[0].Text)).To(Equal(2000))
items, _ = testPartitionMessage(40, limits, 100)
// Now the last word of the first chunk will be concatenated with
// the first word of the second chunk, and so it does not fit in the chunk anymore
Expect(len(items[0].Text)).To(Equal(1994))
Expect(len(items[1].Text)).To(Equal(1999))
Expect(len(items[2].Text)).To(Equal(5))
})
When("the message is empty", func() {
It("should return no items", func() {
items, _ := testPartitionMessage(0, limits, 100)
Expect(items).To(BeEmpty())
})
})
When("given an input without whitespace", func() {
It("should not crash, regardless of length", func() {
unalignedLimits := types.MessageLimit{
ChunkSize: 1997,
ChunkCount: 11,
TotalChunkSize: 5631,
}
testString := ""
for inputLen := 1; inputLen < 8000; inputLen++ {
// add a rune to the string using a repeatable pattern (single digit hex of position)
testString += strconv.FormatInt(int64(inputLen%16), 16)
items, omitted := PartitionMessage(testString, unalignedLimits, 7)
included := 0
for ii, item := range items {
expectedSize := unalignedLimits.ChunkSize
// The last chunk might be smaller than the preceeding chunks
if ii == len(items)-1 {
// the chunk size is the remainder of, the total size,
// or the max size, whatever is smallest,
// and the previous chunk sizes
chunkSize := Min(inputLen, unalignedLimits.TotalChunkSize) % unalignedLimits.ChunkSize
// if the "rest" of the runes needs another chunk
if chunkSize > 0 {
// expect the chunk to contain the "rest" of the runes
expectedSize = chunkSize
}
// the last chunk should never be empty, so treat it as one of the full ones
}
// verify the data, but only on the last chunk to reduce test time
if ii == len(items)-1 {
for ri, r := range item.Text {
runeOffset := (len(item.Text) - ri) - 1
runeVal, err := strconv.ParseInt(string(r), 16, 64)
expectedLen := Min(inputLen, unalignedLimits.TotalChunkSize)
expectedVal := (expectedLen - runeOffset) % 16
Expect(err).ToNot(HaveOccurred())
Expect(runeVal).To(Equal(int64(expectedVal)))
}
}
included += len(item.Text)
Expect(item.Text).To(HaveLen(expectedSize))
}
Expect(omitted + included).To(Equal(inputLen))
}
})
})
})
When("splitting by lines", func() {
It("should return a payload with chunked messages", func() {
items, omitted := testMessageItemsFromLines(18, limits, 2)
Expect(len(items[0].Text)).To(Equal(200))
Expect(len(items[8].Text)).To(Equal(200))
Expect(omitted).To(Equal(0))
})
It("omit characters above total max", func() {
items, omitted := testMessageItemsFromLines(19, limits, 2)
Expect(len(items[0].Text)).To(Equal(200))
Expect(len(items[8].Text)).To(Equal(200))
Expect(omitted).To(Equal(100))
})
It("should trim characters above chunk size", func() {
hundreds := 42
repeat := 21
items, omitted := testMessageItemsFromLines(hundreds, limits, repeat)
Expect(len(items[0].Text)).To(Equal(limits.ChunkSize))
Expect(len(items[1].Text)).To(Equal(limits.ChunkSize))
// Trimmed characters do not count towards the total omitted count
Expect(omitted).To(Equal(0))
})
It("omit characters above total chunk size", func() {
hundreds := 100
repeat := 20
items, omitted := testMessageItemsFromLines(hundreds, limits, repeat)
Expect(len(items[0].Text)).To(Equal(limits.ChunkSize))
Expect(len(items[1].Text)).To(Equal(limits.ChunkSize))
Expect(len(items[2].Text)).To(Equal(limits.ChunkSize))
maxRunes := hundreds * 100
expectedOmitted := maxRunes - limits.TotalChunkSize
Expect(omitted).To(Equal(expectedOmitted))
})
})
})
})
const hundredChars = "this string is exactly (to the letter) a hundred characters long which will make the send func error"
func testMessageItemsFromLines(hundreds int, limits types.MessageLimit, repeat int) (items []types.MessageItem, omitted int) {
builder := strings.Builder{}
ri := 0
for i := 0; i < hundreds; i++ {
builder.WriteString(hundredChars)
ri++
if ri == repeat {
builder.WriteRune('\n')
ri = 0
}
}
items, omitted = MessageItemsFromLines(builder.String(), limits)
maxChunkSize := Min(limits.ChunkSize, repeat*100)
expectedChunkCount := Min(limits.TotalChunkSize/maxChunkSize, Min(hundreds/repeat, limits.ChunkCount-1))
Expect(len(items)).To(Equal(expectedChunkCount), "Chunk count")
return
}
func testPartitionMessage(hundreds int, limits types.MessageLimit, distance int) (items []types.MessageItem, omitted int) {
builder := strings.Builder{}
for i := 0; i < hundreds; i++ {
builder.WriteString(hundredChars)
}
items, omitted = PartitionMessage(builder.String(), limits, distance)
contentSize := Min(hundreds*100, limits.TotalChunkSize)
expectedOmitted := Max(0, (hundreds*100)-contentSize)
ExpectWithOffset(0, omitted).To(Equal(expectedOmitted))
return
}