athenaintegration_coverage_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. package aws
  2. import (
  3. "strings"
  4. "testing"
  5. "github.com/opencost/opencost/pkg/cloud"
  6. )
  7. func TestAthenaIntegration_GetListCostColumn(t *testing.T) {
  8. ai := &AthenaIntegration{}
  9. expected := "SUM(CASE line_item_line_item_type WHEN 'EdpDiscount' THEN 0 WHEN 'PrivateRateDiscount' THEN 0 ELSE line_item_unblended_cost END) as list_cost"
  10. actual := ai.GetListCostColumn()
  11. if actual != expected {
  12. t.Errorf("GetListCostColumn() = %v, want %v", actual, expected)
  13. }
  14. }
  15. func TestAthenaIntegration_GetNetCostColumn(t *testing.T) {
  16. ai := &AthenaIntegration{}
  17. // Test case where net pricing column exists
  18. allColumnsWithNet := map[string]bool{
  19. "line_item_net_unblended_cost": true,
  20. }
  21. expectedWithNet := "SUM(COALESCE(line_item_net_unblended_cost, line_item_unblended_cost, 0)) as net_cost"
  22. actualWithNet := ai.GetNetCostColumn(allColumnsWithNet)
  23. if actualWithNet != expectedWithNet {
  24. t.Errorf("GetNetCostColumn() with net pricing = %v, want %v", actualWithNet, expectedWithNet)
  25. }
  26. // Test case where net pricing column doesn't exist
  27. allColumnsWithoutNet := map[string]bool{
  28. "line_item_unblended_cost": true,
  29. }
  30. expectedWithoutNet := "SUM(line_item_unblended_cost) as net_cost"
  31. actualWithoutNet := ai.GetNetCostColumn(allColumnsWithoutNet)
  32. if actualWithoutNet != expectedWithoutNet {
  33. t.Errorf("GetNetCostColumn() without net pricing = %v, want %v", actualWithoutNet, expectedWithoutNet)
  34. }
  35. }
  36. func TestAthenaIntegration_GetAmortizedCostColumn(t *testing.T) {
  37. ai := &AthenaIntegration{}
  38. allColumns := map[string]bool{
  39. "reservation_effective_cost": true,
  40. "savings_plan_savings_plan_effective_cost": true,
  41. "line_item_unblended_cost": true,
  42. }
  43. result := ai.GetAmortizedCostColumn(allColumns)
  44. if !strings.Contains(result, "SUM(") || !strings.Contains(result, " as amortized_cost") {
  45. t.Errorf("GetAmortizedCostColumn() should return a SUM expression with amortized_cost alias, got: %v", result)
  46. }
  47. }
  48. func TestAthenaIntegration_GetAmortizedNetCostColumn(t *testing.T) {
  49. ai := &AthenaIntegration{}
  50. // Test case where net pricing columns exist
  51. allColumnsWithNet := map[string]bool{
  52. "line_item_net_unblended_cost": true,
  53. "reservation_net_effective_cost": true,
  54. "savings_plan_net_savings_plan_effective_cost": true,
  55. "line_item_unblended_cost": true,
  56. }
  57. resultWithNet := ai.GetAmortizedNetCostColumn(allColumnsWithNet)
  58. if !strings.Contains(resultWithNet, "SUM(") || !strings.Contains(resultWithNet, " as amortized_net_cost") {
  59. t.Errorf("GetAmortizedNetCostColumn() with net pricing should return a SUM expression with amortized_net_cost alias, got: %v", resultWithNet)
  60. }
  61. // Test case where net pricing columns don't exist
  62. allColumnsWithoutNet := map[string]bool{
  63. "reservation_effective_cost": true,
  64. "savings_plan_savings_plan_effective_cost": true,
  65. "line_item_unblended_cost": true,
  66. }
  67. resultWithoutNet := ai.GetAmortizedNetCostColumn(allColumnsWithoutNet)
  68. if !strings.Contains(resultWithoutNet, "SUM(") || !strings.Contains(resultWithoutNet, " as amortized_net_cost") {
  69. t.Errorf("GetAmortizedNetCostColumn() without net pricing should return a SUM expression with amortized_net_cost alias, got: %v", resultWithoutNet)
  70. }
  71. }
  72. func TestAthenaIntegration_GetAmortizedCostCase(t *testing.T) {
  73. ai := &AthenaIntegration{}
  74. // Test case where RI and SP pricing columns exist
  75. allColumnsWithRIAndSP := map[string]bool{
  76. "reservation_effective_cost": true,
  77. "savings_plan_savings_plan_effective_cost": true,
  78. "line_item_unblended_cost": true,
  79. }
  80. resultWithRIAndSP := ai.GetAmortizedCostCase(allColumnsWithRIAndSP)
  81. if !strings.Contains(resultWithRIAndSP, "CASE line_item_line_item_type") ||
  82. !strings.Contains(resultWithRIAndSP, "DiscountedUsage") ||
  83. !strings.Contains(resultWithRIAndSP, "SavingsPlanCoveredUsage") {
  84. t.Errorf("GetAmortizedCostCase() with RI and SP should contain CASE statement with DiscountedUsage and SavingsPlanCoveredUsage, got: %v", resultWithRIAndSP)
  85. }
  86. // Test case where neither RI nor SP pricing columns exist
  87. allColumnsWithoutRIOrSP := map[string]bool{
  88. "line_item_unblended_cost": true,
  89. }
  90. resultWithoutRIOrSP := ai.GetAmortizedCostCase(allColumnsWithoutRIOrSP)
  91. expectedWithoutRIOrSP := "line_item_unblended_cost"
  92. if resultWithoutRIOrSP != expectedWithoutRIOrSP {
  93. t.Errorf("GetAmortizedCostCase() without RI or SP should return line_item_unblended_cost, got: %v, want: %v", resultWithoutRIOrSP, expectedWithoutRIOrSP)
  94. }
  95. }
  96. func TestAthenaIntegration_GetAmortizedNetCostCase(t *testing.T) {
  97. ai := &AthenaIntegration{}
  98. // Test case where net RI and SP pricing columns exist
  99. allColumnsWithNetRIAndSP := map[string]bool{
  100. "reservation_net_effective_cost": true,
  101. "savings_plan_net_savings_plan_effective_cost": true,
  102. "line_item_net_unblended_cost": true,
  103. "line_item_unblended_cost": true,
  104. }
  105. resultWithNetRIAndSP := ai.GetAmortizedNetCostCase(allColumnsWithNetRIAndSP)
  106. if !strings.Contains(resultWithNetRIAndSP, "CASE line_item_line_item_type") ||
  107. !strings.Contains(resultWithNetRIAndSP, "DiscountedUsage") ||
  108. !strings.Contains(resultWithNetRIAndSP, "SavingsPlanCoveredUsage") {
  109. t.Errorf("GetAmortizedNetCostCase() with net RI and SP should contain CASE statement with DiscountedUsage and SavingsPlanCoveredUsage, got: %v", resultWithNetRIAndSP)
  110. }
  111. // Test case where neither net RI nor net SP pricing columns exist
  112. allColumnsWithoutNetRIOrSP := map[string]bool{
  113. "line_item_net_unblended_cost": true,
  114. "line_item_unblended_cost": true,
  115. }
  116. resultWithoutNetRIOrSP := ai.GetAmortizedNetCostCase(allColumnsWithoutNetRIOrSP)
  117. expectedStr := "COALESCE(line_item_net_unblended_cost, line_item_unblended_cost, 0)"
  118. if resultWithoutNetRIOrSP != expectedStr {
  119. t.Errorf("GetAmortizedNetCostCase() without net RI or SP should return COALESCE expression, got: %v, want: %v", resultWithoutNetRIOrSP, expectedStr)
  120. }
  121. }
  122. func TestAthenaIntegration_RemoveColumnAliases(t *testing.T) {
  123. ai := &AthenaIntegration{}
  124. columns := []string{
  125. "column1 as alias1",
  126. "column2",
  127. "column3 as alias3",
  128. "column4",
  129. }
  130. ai.RemoveColumnAliases(columns)
  131. if columns[0] != "column1" {
  132. t.Errorf("RemoveColumnAliases() should remove alias from 'column1 as alias1', got: %v", columns[0])
  133. }
  134. if columns[1] != "column2" {
  135. t.Errorf("RemoveColumnAliases() should not modify 'column2', got: %v", columns[1])
  136. }
  137. if columns[2] != "column3" {
  138. t.Errorf("RemoveColumnAliases() should remove alias from 'column3 as alias3', got: %v", columns[2])
  139. }
  140. if columns[3] != "column4" {
  141. t.Errorf("RemoveColumnAliases() should not modify 'column4', got: %v", columns[3])
  142. }
  143. }
  144. func TestAthenaIntegration_ConvertLabelToAWSTag(t *testing.T) {
  145. ai := &AthenaIntegration{}
  146. // Test case where label already has prefix
  147. labelWithPrefix := "resource_tags_user_test_label"
  148. resultWithPrefix := ai.ConvertLabelToAWSTag(labelWithPrefix)
  149. if resultWithPrefix != labelWithPrefix {
  150. t.Errorf("ConvertLabelToAWSTag() should return label unchanged if it already has prefix, got: %v, want: %v", resultWithPrefix, labelWithPrefix)
  151. }
  152. // Test case where label needs prefix
  153. labelWithoutPrefix := "test.label/with:characters-here"
  154. resultWithoutPrefix := ai.ConvertLabelToAWSTag(labelWithoutPrefix)
  155. expectedWithoutPrefix := "resource_tags_user_test_label_with_characters_here"
  156. if resultWithoutPrefix != expectedWithoutPrefix {
  157. t.Errorf("ConvertLabelToAWSTag() should add prefix and replace characters, got: %v, want: %v", resultWithoutPrefix, expectedWithoutPrefix)
  158. }
  159. }
  160. func TestAthenaIntegration_GetIsKubernetesColumn(t *testing.T) {
  161. ai := &AthenaIntegration{}
  162. // Test with some tag columns present CUR 1.0
  163. allColumnsCur10 := map[string]bool{
  164. "resource_tags_user_eks_cluster_name": true,
  165. "resource_tags_user_alpha_eksctl_io_cluster_name": true,
  166. "resource_tags_user_kubernetes_io_service_name": true,
  167. "some_other_column": true,
  168. }
  169. result := ai.GetIsKubernetesColumn(allColumnsCur10)
  170. if !strings.Contains(result, "line_item_product_code = 'AmazonEKS'") {
  171. t.Errorf("GetIsKubernetesColumn() should always include EKS check, got: %v", result)
  172. }
  173. if !strings.Contains(result, "resource_tags_user_eks_cluster_name <> ''") {
  174. t.Errorf("GetIsKubernetesColumn() should include checks for tag columns, got: %v", result)
  175. }
  176. if !strings.Contains(result, " as is_kubernetes") {
  177. t.Errorf("GetIsKubernetesColumn() should alias result as is_kubernetes, got: %v", result)
  178. }
  179. // Test with some tag columns present CUR 2.0
  180. allColumnsCur20 := map[string]bool{
  181. "resource_tags": true,
  182. "some_other_column": true,
  183. }
  184. result = ai.GetIsKubernetesColumn(allColumnsCur20)
  185. if !strings.Contains(result, "line_item_product_code = 'AmazonEKS'") {
  186. t.Errorf("GetIsKubernetesColumn() should always include EKS check, got: %v", result)
  187. }
  188. if !strings.Contains(result, "COALESCE(resource_tags['user_eks_cluster_name'], '') <> ''") {
  189. t.Errorf("GetIsKubernetesColumn() should include checks for tag columns, got: %v", result)
  190. }
  191. if !strings.Contains(result, " as is_kubernetes") {
  192. t.Errorf("GetIsKubernetesColumn() should alias result as is_kubernetes, got: %v", result)
  193. }
  194. }
  195. func TestAthenaQuerier_GetStatus(t *testing.T) {
  196. aq := &AthenaQuerier{}
  197. // Test initial status
  198. status := aq.GetStatus()
  199. if status.String() != cloud.InitialStatus.String() {
  200. t.Errorf("GetStatus() should return InitialStatus for uninitialized querier, got: %v", status)
  201. }
  202. // Test setting a specific status
  203. aq.ConnectionStatus = cloud.SuccessfulConnection
  204. status = aq.GetStatus()
  205. if status != cloud.SuccessfulConnection {
  206. t.Errorf("GetStatus() should return set status, got: %v", status)
  207. }
  208. }
  209. func TestAthenaQuerier_Equals(t *testing.T) {
  210. aq1 := &AthenaQuerier{
  211. AthenaConfiguration: AthenaConfiguration{
  212. Bucket: "bucket1",
  213. Region: "region1",
  214. Database: "database1",
  215. Table: "table1",
  216. Account: "account1",
  217. Authorizer: &AccessKey{
  218. ID: "id1",
  219. Secret: "secret1",
  220. },
  221. },
  222. }
  223. aq2 := &AthenaQuerier{
  224. AthenaConfiguration: AthenaConfiguration{
  225. Bucket: "bucket1",
  226. Region: "region1",
  227. Database: "database1",
  228. Table: "table1",
  229. Account: "account1",
  230. Authorizer: &AccessKey{
  231. ID: "id1",
  232. Secret: "secret1",
  233. },
  234. },
  235. }
  236. aq3 := &AthenaQuerier{
  237. AthenaConfiguration: AthenaConfiguration{
  238. Bucket: "bucket2", // Different bucket
  239. Region: "region1",
  240. Database: "database1",
  241. Table: "table1",
  242. Account: "account1",
  243. Authorizer: &AccessKey{
  244. ID: "id1",
  245. Secret: "secret1",
  246. },
  247. },
  248. }
  249. // Test equality
  250. if !aq1.Equals(aq2) {
  251. t.Errorf("Equals() should return true for identical configurations")
  252. }
  253. // Test inequality
  254. if aq1.Equals(aq3) {
  255. t.Errorf("Equals() should return false for different configurations")
  256. }
  257. // Test comparison with non-AthenaQuerier
  258. accessKey := &AccessKey{
  259. ID: "id1",
  260. Secret: "secret1",
  261. }
  262. if aq1.Equals(accessKey) {
  263. t.Errorf("Equals() should return false when comparing with different type")
  264. }
  265. }