boaquerier_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. package alibaba
  2. import (
  3. "testing"
  4. "github.com/aliyun/alibaba-cloud-sdk-go/services/bssopenapi"
  5. "github.com/opencost/opencost/core/pkg/opencost"
  6. "github.com/opencost/opencost/pkg/cloud"
  7. )
  8. func TestBoaQuerier_GetStatus(t *testing.T) {
  9. testCases := map[string]struct {
  10. querier BoaQuerier
  11. expectedStatus cloud.ConnectionStatus
  12. }{
  13. "initial status": {
  14. querier: BoaQuerier{},
  15. expectedStatus: cloud.InitialStatus,
  16. },
  17. "existing status": {
  18. querier: BoaQuerier{
  19. ConnectionStatus: cloud.SuccessfulConnection,
  20. },
  21. expectedStatus: cloud.SuccessfulConnection,
  22. },
  23. }
  24. for name, testCase := range testCases {
  25. t.Run(name, func(t *testing.T) {
  26. status := testCase.querier.GetStatus()
  27. if status != testCase.expectedStatus {
  28. t.Errorf("expected status %v, got %v", testCase.expectedStatus, status)
  29. }
  30. })
  31. }
  32. }
  33. func TestBoaQuerier_Equals(t *testing.T) {
  34. baseQuerier := BoaQuerier{
  35. BOAConfiguration: BOAConfiguration{
  36. Account: "account1",
  37. Region: "region1",
  38. Authorizer: &AccessKey{
  39. AccessKeyID: "id1",
  40. AccessKeySecret: "secret1",
  41. },
  42. },
  43. ConnectionStatus: cloud.SuccessfulConnection,
  44. }
  45. testCases := map[string]struct {
  46. left BoaQuerier
  47. right cloud.Config
  48. expected bool
  49. }{
  50. "matching queriers": {
  51. left: baseQuerier,
  52. right: &BoaQuerier{
  53. BOAConfiguration: BOAConfiguration{
  54. Account: "account1",
  55. Region: "region1",
  56. Authorizer: &AccessKey{
  57. AccessKeyID: "id1",
  58. AccessKeySecret: "secret1",
  59. },
  60. },
  61. ConnectionStatus: cloud.SuccessfulConnection,
  62. },
  63. expected: true,
  64. },
  65. "different config": {
  66. left: baseQuerier,
  67. right: &BOAConfiguration{
  68. Account: "account1",
  69. Region: "region1",
  70. },
  71. expected: false,
  72. },
  73. "nil config": {
  74. left: baseQuerier,
  75. right: nil,
  76. expected: false,
  77. },
  78. "different account": {
  79. left: baseQuerier,
  80. right: &BoaQuerier{
  81. BOAConfiguration: BOAConfiguration{
  82. Account: "account2",
  83. Region: "region1",
  84. Authorizer: &AccessKey{
  85. AccessKeyID: "id1",
  86. AccessKeySecret: "secret1",
  87. },
  88. },
  89. },
  90. expected: false,
  91. },
  92. }
  93. for name, testCase := range testCases {
  94. t.Run(name, func(t *testing.T) {
  95. result := testCase.left.Equals(testCase.right)
  96. if result != testCase.expected {
  97. t.Errorf("expected %t, got %t", testCase.expected, result)
  98. }
  99. })
  100. }
  101. }
  102. func TestBoaQuerier_QueryInstanceBill(t *testing.T) {
  103. // This test would require a mock BSS client
  104. // For now, we'll test the function signature and basic error handling
  105. querier := BoaQuerier{
  106. BOAConfiguration: BOAConfiguration{
  107. Account: "test-account",
  108. Region: "test-region",
  109. Authorizer: &AccessKey{
  110. AccessKeyID: "test-id",
  111. AccessKeySecret: "test-secret",
  112. },
  113. },
  114. }
  115. // Test with nil client (should panic or return error)
  116. defer func() {
  117. if r := recover(); r == nil {
  118. // Expected to panic with nil client
  119. }
  120. }()
  121. _, err := querier.QueryInstanceBill(nil, true, "https", "DAILY", "2023-01", "2023-01-01", 1)
  122. if err == nil {
  123. t.Error("expected error with nil client")
  124. }
  125. }
  126. func TestBoaQuerier_QueryBoaPaginated(t *testing.T) {
  127. querier := BoaQuerier{
  128. BOAConfiguration: BOAConfiguration{
  129. Account: "test-account",
  130. Region: "test-region",
  131. Authorizer: &AccessKey{
  132. AccessKeyID: "test-id",
  133. AccessKeySecret: "test-secret",
  134. },
  135. },
  136. }
  137. // Test with nil client - this will panic, so we need to recover
  138. defer func() {
  139. if r := recover(); r == nil {
  140. t.Error("expected panic with nil client")
  141. }
  142. }()
  143. querier.QueryBoaPaginated(nil, true, "https", "DAILY", "2023-01", "2023-01-01", func(*bssopenapi.QueryInstanceBillResponse) bool {
  144. return true
  145. })
  146. }
  147. func TestGetBoaQueryInstanceBillFunc(t *testing.T) {
  148. testCases := map[string]struct {
  149. response *bssopenapi.QueryInstanceBillResponse
  150. expectedReturn bool
  151. callCount int
  152. }{
  153. "nil response": {
  154. response: nil,
  155. expectedReturn: false,
  156. callCount: 0,
  157. },
  158. "empty response": {
  159. response: &bssopenapi.QueryInstanceBillResponse{},
  160. expectedReturn: false,
  161. callCount: 0,
  162. },
  163. "valid response with items": {
  164. response: &bssopenapi.QueryInstanceBillResponse{},
  165. expectedReturn: false,
  166. callCount: 0,
  167. },
  168. }
  169. for name, testCase := range testCases {
  170. t.Run(name, func(t *testing.T) {
  171. callCount := 0
  172. handler := func(item bssopenapi.Item) error {
  173. callCount++
  174. return nil
  175. }
  176. processor := GetBoaQueryInstanceBillFunc(handler, "2023-01-01")
  177. result := processor(testCase.response)
  178. if result != testCase.expectedReturn {
  179. t.Errorf("expected return %t, got %t", testCase.expectedReturn, result)
  180. }
  181. if callCount != testCase.callCount {
  182. t.Errorf("expected %d calls to handler, got %d", testCase.callCount, callCount)
  183. }
  184. })
  185. }
  186. }
  187. func TestSelectAlibabaCategory(t *testing.T) {
  188. testCases := map[string]struct {
  189. item bssopenapi.Item
  190. expectedCategory string
  191. }{
  192. "empty item": {
  193. item: bssopenapi.Item{},
  194. expectedCategory: opencost.OtherCategory,
  195. },
  196. "node instance": {
  197. item: bssopenapi.Item{
  198. InstanceID: "i-test123",
  199. },
  200. expectedCategory: opencost.ComputeCategory,
  201. },
  202. "disk instance": {
  203. item: bssopenapi.Item{
  204. InstanceID: "d-test123",
  205. },
  206. expectedCategory: opencost.StorageCategory,
  207. },
  208. "network usage": {
  209. item: bssopenapi.Item{
  210. UsageUnit: "piece",
  211. },
  212. expectedCategory: opencost.NetworkCategory,
  213. },
  214. "SLB product": {
  215. item: bssopenapi.Item{
  216. ProductCode: "slb",
  217. },
  218. expectedCategory: opencost.NetworkCategory,
  219. },
  220. "EIP product": {
  221. item: bssopenapi.Item{
  222. ProductCode: "eip",
  223. },
  224. expectedCategory: opencost.NetworkCategory,
  225. },
  226. "NIS product": {
  227. item: bssopenapi.Item{
  228. ProductCode: "nis",
  229. },
  230. expectedCategory: opencost.NetworkCategory,
  231. },
  232. "GTM product": {
  233. item: bssopenapi.Item{
  234. ProductCode: "gtm",
  235. },
  236. expectedCategory: opencost.NetworkCategory,
  237. },
  238. "ECS product": {
  239. item: bssopenapi.Item{
  240. ProductCode: "ecs",
  241. },
  242. expectedCategory: opencost.ComputeCategory,
  243. },
  244. "EDS product": {
  245. item: bssopenapi.Item{
  246. ProductCode: "eds",
  247. },
  248. expectedCategory: opencost.ComputeCategory,
  249. },
  250. "SAS product": {
  251. item: bssopenapi.Item{
  252. ProductCode: "sas",
  253. },
  254. expectedCategory: opencost.ComputeCategory,
  255. },
  256. "ACK product": {
  257. item: bssopenapi.Item{
  258. ProductCode: "ack",
  259. },
  260. expectedCategory: opencost.ManagementCategory,
  261. },
  262. "EBS product": {
  263. item: bssopenapi.Item{
  264. ProductCode: "ebs",
  265. },
  266. expectedCategory: opencost.StorageCategory,
  267. },
  268. "OSS product": {
  269. item: bssopenapi.Item{
  270. ProductCode: "oss",
  271. },
  272. expectedCategory: opencost.StorageCategory,
  273. },
  274. "SCU product": {
  275. item: bssopenapi.Item{
  276. ProductCode: "scu",
  277. },
  278. expectedCategory: opencost.StorageCategory,
  279. },
  280. "unknown product": {
  281. item: bssopenapi.Item{
  282. ProductCode: "unknown",
  283. },
  284. expectedCategory: opencost.OtherCategory,
  285. },
  286. "case insensitive product code": {
  287. item: bssopenapi.Item{
  288. ProductCode: "SLB",
  289. },
  290. expectedCategory: opencost.NetworkCategory,
  291. },
  292. "node with network usage (node takes precedence)": {
  293. item: bssopenapi.Item{
  294. InstanceID: "i-test123",
  295. UsageUnit: "piece",
  296. },
  297. expectedCategory: opencost.ComputeCategory,
  298. },
  299. "disk with network usage (disk takes precedence)": {
  300. item: bssopenapi.Item{
  301. InstanceID: "d-test123",
  302. UsageUnit: "piece",
  303. },
  304. expectedCategory: opencost.StorageCategory,
  305. },
  306. }
  307. for name, testCase := range testCases {
  308. t.Run(name, func(t *testing.T) {
  309. result := SelectAlibabaCategory(testCase.item)
  310. if result != testCase.expectedCategory {
  311. t.Errorf("expected category %s, got %s", testCase.expectedCategory, result)
  312. }
  313. })
  314. }
  315. }
  316. func TestBoaQuerier_Integration(t *testing.T) {
  317. // Test that BoaQuerier properly embeds BOAConfiguration
  318. querier := BoaQuerier{
  319. BOAConfiguration: BOAConfiguration{
  320. Account: "test-account",
  321. Region: "test-region",
  322. Authorizer: &AccessKey{
  323. AccessKeyID: "test-id",
  324. AccessKeySecret: "test-secret",
  325. },
  326. },
  327. ConnectionStatus: cloud.SuccessfulConnection,
  328. }
  329. // Test that we can access BOAConfiguration methods
  330. if querier.Account != "test-account" {
  331. t.Errorf("expected account test-account, got %s", querier.Account)
  332. }
  333. if querier.Region != "test-region" {
  334. t.Errorf("expected region test-region, got %s", querier.Region)
  335. }
  336. // Test Key method from embedded BOAConfiguration
  337. key := querier.Key()
  338. expectedKey := "test-account/test-region"
  339. if key != expectedKey {
  340. t.Errorf("expected key %s, got %s", expectedKey, key)
  341. }
  342. // Test Provider method from embedded BOAConfiguration
  343. provider := querier.Provider()
  344. if provider != opencost.AlibabaProvider {
  345. t.Errorf("expected provider %s, got %s", opencost.AlibabaProvider, provider)
  346. }
  347. }