decoder_test.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. package exporter
  2. import (
  3. "compress/gzip"
  4. "reflect"
  5. "testing"
  6. "time"
  7. "github.com/opencost/opencost/core/pkg/diagnostics"
  8. "github.com/opencost/opencost/core/pkg/heartbeat"
  9. "github.com/opencost/opencost/core/pkg/model"
  10. "github.com/opencost/opencost/core/pkg/model/pb"
  11. "github.com/opencost/opencost/core/pkg/opencost"
  12. "github.com/opencost/opencost/core/pkg/storage"
  13. "github.com/opencost/opencost/core/pkg/util"
  14. "github.com/opencost/opencost/core/pkg/util/json"
  15. "google.golang.org/protobuf/proto"
  16. )
  17. type decoderTestCase[T any] struct {
  18. name string
  19. data []byte
  20. want *T
  21. wantErr bool
  22. }
  23. func generateBadBytes() []byte {
  24. buff := util.NewBuffer()
  25. for i := 0; i < 10; i++ {
  26. buff.WriteUInt64(9999999)
  27. }
  28. return buff.Bytes()
  29. }
  30. func TestBingenDecoder(t *testing.T) {
  31. badBytes := generateBadBytes()
  32. now := time.Now().UTC().Truncate(24 * time.Hour)
  33. start := now.Add(-(24 * 5) * time.Hour)
  34. // Define and Run Allocation Tests
  35. allocSet := opencost.GenerateMockAllocationSet(start)
  36. allocSetRaw, err := allocSet.MarshalBinary()
  37. if err != nil {
  38. t.Errorf("failed to marshal allocation set: %s", err.Error())
  39. }
  40. allocTests := []decoderTestCase[opencost.AllocationSet]{
  41. {
  42. name: "allocation valid",
  43. data: allocSetRaw,
  44. want: allocSet,
  45. wantErr: false,
  46. },
  47. {
  48. name: "allocation invalid",
  49. data: badBytes,
  50. want: nil,
  51. wantErr: true,
  52. },
  53. }
  54. testDecoder(t, BingenDecoder, allocTests)
  55. // Define and Run Asset Tests
  56. assetSet := opencost.GenerateMockAssetSet(start, 24*time.Hour)
  57. assetSetRaw, err := assetSet.MarshalBinary()
  58. if err != nil {
  59. t.Errorf("failed to marshal asset set: %s", err.Error())
  60. }
  61. assetTests := []decoderTestCase[opencost.AssetSet]{
  62. {
  63. name: "asset valid",
  64. data: assetSetRaw,
  65. want: assetSet,
  66. wantErr: false,
  67. },
  68. {
  69. name: "asset invalid",
  70. data: badBytes,
  71. want: nil,
  72. wantErr: true,
  73. },
  74. }
  75. testDecoder(t, BingenDecoder, assetTests)
  76. // Define and Run Cloud Cost Tests
  77. CloudCostSet := opencost.GenerateMockCloudCostSet(start, start.Add(24*time.Hour), "gcp", "gke")
  78. CloudCostSetRaw, err := CloudCostSet.MarshalBinary()
  79. if err != nil {
  80. t.Errorf("failed to marshal cloud cost set: %s", err.Error())
  81. }
  82. cloudCostTests := []decoderTestCase[opencost.CloudCostSet]{
  83. {
  84. name: "cloud cost valid",
  85. data: CloudCostSetRaw,
  86. want: CloudCostSet,
  87. wantErr: false,
  88. },
  89. {
  90. name: "cloud cost invalid",
  91. data: badBytes,
  92. want: nil,
  93. wantErr: true,
  94. },
  95. }
  96. testDecoder(t, BingenDecoder, cloudCostTests)
  97. // Define and Run Network Insight Tests
  98. networkInsightSet := opencost.GenerateMockNetworkInsightSet(start, start.Add(24*time.Hour))
  99. networkInsightSetRaw, err := networkInsightSet.MarshalBinary()
  100. if err != nil {
  101. t.Errorf("failed to marshal network insight set: %s", err.Error())
  102. }
  103. networkInsightTests := []decoderTestCase[opencost.NetworkInsightSet]{
  104. {
  105. name: "network insight valid",
  106. data: networkInsightSetRaw,
  107. want: networkInsightSet,
  108. wantErr: false,
  109. },
  110. {
  111. name: "network insight invalid",
  112. data: badBytes,
  113. want: nil,
  114. wantErr: true,
  115. },
  116. }
  117. testDecoder(t, BingenDecoder, networkInsightTests)
  118. }
  119. func TestJsonDecoder(t *testing.T) {
  120. badBytes := generateBadBytes()
  121. now := time.Now().UTC().Truncate(24 * time.Hour)
  122. start := now.Add(-(24 * 5) * time.Hour)
  123. hb := heartbeat.Heartbeat{
  124. Id: "heartBeatID",
  125. Timestamp: start,
  126. Uptime: 123,
  127. Application: "mock",
  128. Version: "test",
  129. Metadata: map[string]any{
  130. "str": "test",
  131. "num": 1.0,
  132. },
  133. }
  134. hbraw, err := json.Marshal(hb)
  135. if err != nil {
  136. t.Errorf("failed to marshal heartbeat: %s", err.Error())
  137. }
  138. heartbeatTests := []decoderTestCase[heartbeat.Heartbeat]{
  139. {
  140. name: "heartbeat valid",
  141. data: hbraw,
  142. want: &hb,
  143. wantErr: false,
  144. },
  145. {
  146. name: "heartbeat invalid",
  147. data: badBytes,
  148. want: nil,
  149. wantErr: true,
  150. },
  151. }
  152. testDecoder(t, JSONDecoder, heartbeatTests)
  153. }
  154. func TestGzipDecoder(t *testing.T) {
  155. badBytes := generateBadBytes()
  156. now := time.Now().UTC().Truncate(24 * time.Hour)
  157. start := now.Add(-(24 * 5) * time.Hour)
  158. diag := diagnostics.DiagnosticResult{
  159. ID: "diagnosticID",
  160. Name: "diagnisticName",
  161. Description: "Test Diagnostic",
  162. Category: "test",
  163. Timestamp: start,
  164. Error: "test",
  165. Details: map[string]any{
  166. "str": "test",
  167. "num": 1.0,
  168. },
  169. }
  170. diagRaw, err := json.Marshal(diag)
  171. if err != nil {
  172. t.Errorf("failed to marshal diagnostic: %s", err.Error())
  173. }
  174. diagCompressed, err := gZipEncode(diagRaw, gzip.BestSpeed)
  175. if err != nil {
  176. t.Errorf("failed to compress diagnostic: %s", err.Error())
  177. }
  178. badCompressed, err := gZipEncode(badBytes, gzip.BestSpeed)
  179. if err != nil {
  180. t.Errorf("failed to compress bad bytes: %s", err.Error())
  181. }
  182. diagnosticTests := []decoderTestCase[diagnostics.DiagnosticResult]{
  183. {
  184. name: "diagnostic valid",
  185. data: diagCompressed,
  186. want: &diag,
  187. wantErr: false,
  188. },
  189. {
  190. name: "diagnostic invalid",
  191. data: badCompressed,
  192. want: nil,
  193. wantErr: true,
  194. },
  195. {
  196. name: "diagnostic bypass valid",
  197. data: diagRaw,
  198. want: &diag,
  199. wantErr: false,
  200. },
  201. {
  202. name: "diagnostic bypass invalid",
  203. data: badBytes,
  204. want: nil,
  205. wantErr: true,
  206. },
  207. }
  208. testDecoder(t, GetGzipDecoder[diagnostics.DiagnosticResult](JSONDecoder), diagnosticTests)
  209. }
  210. func TestProtobufDecoder(t *testing.T) {
  211. badBytes := generateBadBytes()
  212. now := time.Now().UTC().Truncate(24 * time.Hour)
  213. start := now.Add(-(24 * 5) * time.Hour)
  214. customCostSet := model.GenerateMockCustomCostSet(start, start.Add(24*time.Hour))
  215. customCostSetRaw, err := proto.Marshal(customCostSet)
  216. if err != nil {
  217. t.Errorf("failed to marshal custom cost set: %s", err.Error())
  218. }
  219. customCostTests := []decoderTestCase[pb.CustomCostResponse]{
  220. {
  221. name: "custom cost valid",
  222. data: customCostSetRaw,
  223. want: customCostSet,
  224. wantErr: false,
  225. },
  226. {
  227. name: "custom cost invalid",
  228. data: badBytes,
  229. want: nil,
  230. wantErr: true,
  231. },
  232. }
  233. testProtoBufDecoder(t, ProtobufDecoder, customCostTests)
  234. labelsResponse := model.GenerateMockLabelResponse(start, pb.Resolution_RESOLUTION_1D)
  235. labelsResponseRaw, err := proto.Marshal(labelsResponse)
  236. if err != nil {
  237. t.Errorf("failed to marshal custom cost set: %s", err.Error())
  238. }
  239. labelsResponseTests := []decoderTestCase[pb.LabelsResponse]{
  240. {
  241. name: "labels response valid",
  242. data: labelsResponseRaw,
  243. want: labelsResponse,
  244. wantErr: false,
  245. },
  246. {
  247. name: "labels response invalid",
  248. data: badBytes,
  249. want: nil,
  250. wantErr: true,
  251. },
  252. }
  253. testProtoBufDecoder(t, ProtobufDecoder, labelsResponseTests)
  254. }
  255. func TestProtobufEncoderDecoderRoundTrip(t *testing.T) {
  256. badBytes := generateBadBytes()
  257. now := time.Now().UTC().Truncate(24 * time.Hour)
  258. start := now.Add(-(24 * 5) * time.Hour)
  259. store := storage.NewMemoryStorage()
  260. writer, err := store.WriteStream("test.pb")
  261. if err != nil {
  262. t.Fatalf("failed to open writer: %s", err)
  263. return
  264. }
  265. customCostSet := model.GenerateMockCustomCostSet(start, start.Add(24*time.Hour))
  266. enc := NewProtobufEncoder[pb.CustomCostResponse]()
  267. err = enc.EncodeTo(writer, customCostSet)
  268. if err != nil {
  269. _ = writer.Close()
  270. t.Fatalf("Failed to encode to writer: %s", err)
  271. return
  272. }
  273. if err = writer.Close(); err != nil {
  274. t.Fatalf("failed to flush/close the writer; %s", err)
  275. return
  276. }
  277. // load raw bytes from memory file system
  278. customCostSetRaw, err := store.Read("test.pb")
  279. if err != nil {
  280. t.Errorf("failed to load custom cost set raw binary from memory disk: %s", err)
  281. return
  282. }
  283. customCostTests := []decoderTestCase[pb.CustomCostResponse]{
  284. {
  285. name: "custom cost valid",
  286. data: customCostSetRaw,
  287. want: customCostSet,
  288. wantErr: false,
  289. },
  290. {
  291. name: "custom cost invalid",
  292. data: badBytes,
  293. want: nil,
  294. wantErr: true,
  295. },
  296. }
  297. testProtoBufDecoder(t, ProtobufDecoder, customCostTests)
  298. labelsResponse := model.GenerateMockLabelResponse(start, pb.Resolution_RESOLUTION_1D)
  299. labelsEnc := NewProtobufEncoder[pb.LabelsResponse]()
  300. labelsWriter, err := store.WriteStream("test-labels.pb")
  301. if err != nil {
  302. t.Fatalf("failed to open labels writer: %s", err)
  303. return
  304. }
  305. err = labelsEnc.EncodeTo(labelsWriter, labelsResponse)
  306. if err != nil {
  307. _ = labelsWriter.Close()
  308. t.Fatalf("Failed to encode to labels writer: %s", err)
  309. return
  310. }
  311. if err = labelsWriter.Close(); err != nil {
  312. t.Fatalf("failed to flush/close the labels writer; %s", err)
  313. return
  314. }
  315. labelsResponseRaw, err := store.Read("test-labels.pb")
  316. if err != nil {
  317. t.Fatalf("failed to marshal labels response: %s", err)
  318. return
  319. }
  320. labelsResponseTests := []decoderTestCase[pb.LabelsResponse]{
  321. {
  322. name: "labels response valid",
  323. data: labelsResponseRaw,
  324. want: labelsResponse,
  325. wantErr: false,
  326. },
  327. {
  328. name: "labels response invalid",
  329. data: badBytes,
  330. want: nil,
  331. wantErr: true,
  332. },
  333. }
  334. testProtoBufDecoder(t, ProtobufDecoder, labelsResponseTests)
  335. }
  336. func testProtoBufDecoder[T any, U ProtoMessagePtr[T]](t *testing.T, decoder Decoder[T], testCases []decoderTestCase[T]) {
  337. for _, tt := range testCases {
  338. t.Run(tt.name, func(t *testing.T) {
  339. got, err := decoder(tt.data)
  340. if (err != nil) != tt.wantErr {
  341. t.Errorf("Decoder() error = %v, wantErr %v", err, tt.wantErr)
  342. if err != nil {
  343. t.Errorf("Error: %s", err.Error())
  344. }
  345. return
  346. }
  347. if !proto.Equal(U(got), U(tt.want)) {
  348. t.Errorf("Decoder() got = %v, want %v", got, tt.want)
  349. }
  350. })
  351. }
  352. }
  353. func testDecoder[T any](t *testing.T, decoder Decoder[T], testCases []decoderTestCase[T]) {
  354. for _, tt := range testCases {
  355. t.Run(tt.name, func(t *testing.T) {
  356. got, err := decoder(tt.data)
  357. if (err != nil) != tt.wantErr {
  358. t.Errorf("Decoder() error = %v, wantErr %v", err, tt.wantErr)
  359. if err != nil {
  360. t.Errorf("Error: %s", err.Error())
  361. }
  362. return
  363. }
  364. if !reflect.DeepEqual(got, tt.want) {
  365. t.Errorf("Decoder() got = %v, want %v", got, tt.want)
  366. }
  367. })
  368. }
  369. }