bigqueryconfiguration_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. package gcp
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/opencost/opencost/core/pkg/log"
  6. "github.com/opencost/opencost/core/pkg/util/json"
  7. "github.com/opencost/opencost/pkg/cloud"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestBigQueryConfiguration_Validate(t *testing.T) {
  12. testCases := map[string]struct {
  13. config BigQueryConfiguration
  14. expected error
  15. }{
  16. "valid config GCP Key": {
  17. config: BigQueryConfiguration{
  18. ProjectID: "projectID",
  19. Dataset: "dataset",
  20. Table: "table",
  21. Authorizer: &ServiceAccountKey{
  22. Key: map[string]string{
  23. "Key": "Key",
  24. "key1": "key2",
  25. },
  26. },
  27. },
  28. expected: nil,
  29. },
  30. "valid config WorkloadIdentity": {
  31. config: BigQueryConfiguration{
  32. ProjectID: "projectID",
  33. Dataset: "dataset",
  34. Table: "table",
  35. Authorizer: &WorkloadIdentity{},
  36. },
  37. expected: nil,
  38. },
  39. "access Key invalid": {
  40. config: BigQueryConfiguration{
  41. ProjectID: "projectID",
  42. Dataset: "dataset",
  43. Table: "table",
  44. Authorizer: &ServiceAccountKey{
  45. Key: nil,
  46. },
  47. },
  48. expected: fmt.Errorf("BigQueryConfig: issue with GCP Authorizer: ServiceAccountKey: missing Key"),
  49. },
  50. "missing configurer": {
  51. config: BigQueryConfiguration{
  52. ProjectID: "projectID",
  53. Dataset: "dataset",
  54. Table: "table",
  55. Authorizer: nil,
  56. },
  57. expected: fmt.Errorf("BigQueryConfig: missing configurer"),
  58. },
  59. "missing projectID": {
  60. config: BigQueryConfiguration{
  61. ProjectID: "",
  62. Dataset: "dataset",
  63. Table: "table",
  64. Authorizer: &ServiceAccountKey{
  65. Key: map[string]string{
  66. "Key": "Key",
  67. "key1": "key2",
  68. },
  69. },
  70. },
  71. expected: fmt.Errorf("BigQueryConfig: missing ProjectID"),
  72. },
  73. "missing dataset": {
  74. config: BigQueryConfiguration{
  75. ProjectID: "projectID",
  76. Dataset: "",
  77. Table: "table",
  78. Authorizer: &ServiceAccountKey{
  79. Key: map[string]string{
  80. "Key": "Key",
  81. "key1": "key2",
  82. },
  83. },
  84. },
  85. expected: fmt.Errorf("BigQueryConfig: missing Dataset"),
  86. },
  87. "missing table": {
  88. config: BigQueryConfiguration{
  89. ProjectID: "projectID",
  90. Dataset: "dataset",
  91. Table: "",
  92. Authorizer: &ServiceAccountKey{
  93. Key: map[string]string{
  94. "Key": "Key",
  95. "key1": "key2",
  96. },
  97. },
  98. },
  99. expected: fmt.Errorf("BigQueryConfig: missing Table"),
  100. },
  101. }
  102. for name, testCase := range testCases {
  103. t.Run(name, func(t *testing.T) {
  104. actual := testCase.config.Validate()
  105. actualString := "nil"
  106. if actual != nil {
  107. actualString = actual.Error()
  108. }
  109. expectedString := "nil"
  110. if testCase.expected != nil {
  111. expectedString = testCase.expected.Error()
  112. }
  113. if actualString != expectedString {
  114. t.Errorf("errors do not match: Actual: '%s', Expected: '%s", actualString, expectedString)
  115. }
  116. })
  117. }
  118. }
  119. func TestBigQueryConfiguration_Equals(t *testing.T) {
  120. testCases := map[string]struct {
  121. left BigQueryConfiguration
  122. right cloud.Config
  123. expected bool
  124. }{
  125. "matching config": {
  126. left: BigQueryConfiguration{
  127. ProjectID: "projectID",
  128. Dataset: "dataset",
  129. Table: "table",
  130. Authorizer: &ServiceAccountKey{
  131. Key: map[string]string{
  132. "Key": "Key",
  133. "key1": "key2",
  134. },
  135. },
  136. },
  137. right: &BigQueryConfiguration{
  138. ProjectID: "projectID",
  139. Dataset: "dataset",
  140. Table: "table",
  141. Authorizer: &ServiceAccountKey{
  142. Key: map[string]string{
  143. "Key": "Key",
  144. "key1": "key2",
  145. },
  146. },
  147. },
  148. expected: true,
  149. },
  150. "different configurer": {
  151. left: BigQueryConfiguration{
  152. ProjectID: "projectID",
  153. Dataset: "dataset",
  154. Table: "table",
  155. Authorizer: &ServiceAccountKey{
  156. Key: map[string]string{
  157. "Key": "Key",
  158. "key1": "key2",
  159. },
  160. },
  161. },
  162. right: &BigQueryConfiguration{
  163. ProjectID: "projectID",
  164. Dataset: "dataset",
  165. Table: "table",
  166. Authorizer: &WorkloadIdentity{},
  167. },
  168. expected: false,
  169. },
  170. "missing both configurer": {
  171. left: BigQueryConfiguration{
  172. ProjectID: "projectID",
  173. Dataset: "dataset",
  174. Table: "table",
  175. Authorizer: nil,
  176. },
  177. right: &BigQueryConfiguration{
  178. ProjectID: "projectID",
  179. Dataset: "dataset",
  180. Table: "table",
  181. Authorizer: nil,
  182. },
  183. expected: true,
  184. },
  185. "missing left configurer": {
  186. left: BigQueryConfiguration{
  187. ProjectID: "projectID",
  188. Dataset: "dataset",
  189. Table: "table",
  190. Authorizer: nil,
  191. },
  192. right: &BigQueryConfiguration{
  193. ProjectID: "projectID",
  194. Dataset: "dataset",
  195. Table: "table",
  196. Authorizer: &WorkloadIdentity{},
  197. },
  198. expected: false,
  199. },
  200. "missing right configurer": {
  201. left: BigQueryConfiguration{
  202. ProjectID: "projectID",
  203. Dataset: "dataset",
  204. Table: "table",
  205. Authorizer: &ServiceAccountKey{
  206. Key: map[string]string{
  207. "Key": "Key",
  208. "key1": "key2",
  209. },
  210. },
  211. },
  212. right: &BigQueryConfiguration{
  213. ProjectID: "projectID",
  214. Dataset: "dataset",
  215. Table: "table",
  216. Authorizer: nil,
  217. },
  218. expected: false,
  219. },
  220. "different projectID": {
  221. left: BigQueryConfiguration{
  222. ProjectID: "projectID",
  223. Dataset: "dataset",
  224. Table: "table",
  225. Authorizer: &ServiceAccountKey{
  226. Key: map[string]string{
  227. "Key": "Key",
  228. "key1": "key2",
  229. },
  230. },
  231. },
  232. right: &BigQueryConfiguration{
  233. ProjectID: "projectID2",
  234. Dataset: "dataset",
  235. Table: "table",
  236. Authorizer: &ServiceAccountKey{
  237. Key: map[string]string{
  238. "Key": "Key",
  239. "key1": "key2",
  240. },
  241. },
  242. },
  243. expected: false,
  244. },
  245. "different dataset": {
  246. left: BigQueryConfiguration{
  247. ProjectID: "projectID",
  248. Dataset: "dataset",
  249. Table: "table",
  250. Authorizer: &ServiceAccountKey{
  251. Key: map[string]string{
  252. "Key": "Key",
  253. "key1": "key2",
  254. },
  255. },
  256. },
  257. right: &BigQueryConfiguration{
  258. ProjectID: "projectID",
  259. Dataset: "dataset2",
  260. Table: "table",
  261. Authorizer: &ServiceAccountKey{
  262. Key: map[string]string{
  263. "Key": "Key",
  264. "key1": "key2",
  265. },
  266. },
  267. },
  268. expected: false,
  269. },
  270. "different table": {
  271. left: BigQueryConfiguration{
  272. ProjectID: "projectID",
  273. Dataset: "dataset",
  274. Table: "table",
  275. Authorizer: &ServiceAccountKey{
  276. Key: map[string]string{
  277. "Key": "Key",
  278. "key1": "key2",
  279. },
  280. },
  281. },
  282. right: &BigQueryConfiguration{
  283. ProjectID: "projectID",
  284. Dataset: "dataset",
  285. Table: "table2",
  286. Authorizer: &ServiceAccountKey{
  287. Key: map[string]string{
  288. "Key": "Key",
  289. "key1": "key2",
  290. },
  291. },
  292. },
  293. expected: false,
  294. },
  295. "different config": {
  296. left: BigQueryConfiguration{
  297. ProjectID: "projectID",
  298. Dataset: "dataset",
  299. Table: "table",
  300. Authorizer: &ServiceAccountKey{
  301. Key: map[string]string{
  302. "Key": "Key",
  303. "key1": "key2",
  304. },
  305. },
  306. },
  307. right: &ServiceAccountKey{
  308. Key: map[string]string{
  309. "Key": "Key",
  310. "key1": "key2",
  311. },
  312. },
  313. expected: false,
  314. },
  315. }
  316. for name, testCase := range testCases {
  317. t.Run(name, func(t *testing.T) {
  318. actual := testCase.left.Equals(testCase.right)
  319. if actual != testCase.expected {
  320. t.Errorf("incorrect result: Actual: '%t', Expected: '%t", actual, testCase.expected)
  321. }
  322. })
  323. }
  324. }
  325. func TestBigQueryConfiguration_JSON(t *testing.T) {
  326. testCases := map[string]struct {
  327. config BigQueryConfiguration
  328. }{
  329. "Empty Config": {
  330. config: BigQueryConfiguration{},
  331. },
  332. "Nil Authorizer": {
  333. config: BigQueryConfiguration{
  334. ProjectID: "projectID",
  335. Dataset: "dataset",
  336. Table: "table",
  337. Authorizer: nil,
  338. },
  339. },
  340. "ServiceAccountKeyConfigurer": {
  341. config: BigQueryConfiguration{
  342. ProjectID: "projectID",
  343. Dataset: "dataset",
  344. Table: "table",
  345. Authorizer: &ServiceAccountKey{
  346. Key: map[string]string{
  347. "Key": "Key",
  348. "key1": "key2",
  349. },
  350. },
  351. },
  352. },
  353. "WorkLoadIdentityConfigurer": {
  354. config: BigQueryConfiguration{
  355. ProjectID: "projectID",
  356. Dataset: "dataset",
  357. Table: "table",
  358. Authorizer: &WorkloadIdentity{},
  359. },
  360. },
  361. }
  362. for name, testCase := range testCases {
  363. t.Run(name, func(t *testing.T) {
  364. // test JSON Marshalling
  365. configJSON, err := json.Marshal(testCase.config)
  366. if err != nil {
  367. t.Errorf("failed to marshal configuration: %s", err.Error())
  368. }
  369. log.Info(string(configJSON))
  370. unmarshalledConfig := &BigQueryConfiguration{}
  371. err = json.Unmarshal(configJSON, unmarshalledConfig)
  372. if err != nil {
  373. t.Errorf("failed to unmarshal configuration: %s", err.Error())
  374. }
  375. if !testCase.config.Equals(unmarshalledConfig) {
  376. t.Error("config does not equal unmarshalled config")
  377. }
  378. })
  379. }
  380. }
  381. func TestBigQueryConfiguration_Key(t *testing.T) {
  382. bqc := &BigQueryConfiguration{
  383. ProjectID: "test-project",
  384. Dataset: "test-dataset",
  385. Table: "test-table",
  386. }
  387. key := bqc.Key()
  388. expected := "test-project/test-dataset.test-table"
  389. assert.Equal(t, expected, key)
  390. }
  391. func TestBigQueryConfiguration_Provider(t *testing.T) {
  392. bqc := &BigQueryConfiguration{}
  393. provider := bqc.Provider()
  394. assert.Equal(t, "GCP", provider)
  395. }
  396. func TestBigQueryConfiguration_GetBillingDataDataset(t *testing.T) {
  397. bqc := &BigQueryConfiguration{
  398. Dataset: "test-dataset",
  399. Table: "test-table",
  400. }
  401. dataset := bqc.GetBillingDataDataset()
  402. expected := "test-dataset.test-table"
  403. assert.Equal(t, expected, dataset)
  404. }
  405. func TestBigQueryConfiguration_Sanitize(t *testing.T) {
  406. bqc := &BigQueryConfiguration{
  407. ProjectID: "test-project",
  408. Dataset: "test-dataset",
  409. Table: "test-table",
  410. Authorizer: &ServiceAccountKey{
  411. Key: map[string]string{
  412. "type": "service_account",
  413. "private_key": "secret-key",
  414. },
  415. },
  416. }
  417. sanitized := bqc.Sanitize()
  418. require.NotNil(t, sanitized)
  419. sanitizedBQC, ok := sanitized.(*BigQueryConfiguration)
  420. require.True(t, ok)
  421. assert.Equal(t, "test-project", sanitizedBQC.ProjectID)
  422. assert.Equal(t, "test-dataset", sanitizedBQC.Dataset)
  423. assert.Equal(t, "test-table", sanitizedBQC.Table)
  424. assert.NotNil(t, sanitizedBQC.Authorizer)
  425. // Check that the authorizer is also sanitized
  426. saKey, ok := sanitizedBQC.Authorizer.(*ServiceAccountKey)
  427. require.True(t, ok)
  428. for _, value := range saKey.Key {
  429. assert.Equal(t, cloud.Redacted, value)
  430. }
  431. }
  432. func TestConvertBigQueryConfigToConfig(t *testing.T) {
  433. tests := []struct {
  434. name string
  435. bqc BigQueryConfig
  436. expected cloud.KeyedConfig
  437. }{
  438. {
  439. name: "Empty config",
  440. bqc: BigQueryConfig{},
  441. expected: nil,
  442. },
  443. {
  444. name: "Config with service account key",
  445. bqc: BigQueryConfig{
  446. ProjectID: "test-project",
  447. BillingDataDataset: "test-dataset.test-table",
  448. Key: map[string]string{
  449. "type": "service_account",
  450. },
  451. },
  452. expected: &BigQueryConfiguration{
  453. ProjectID: "test-project",
  454. Dataset: "test-dataset",
  455. Table: "test-table",
  456. Authorizer: &ServiceAccountKey{
  457. Key: map[string]string{
  458. "type": "service_account",
  459. },
  460. },
  461. },
  462. },
  463. {
  464. name: "Config without service account key",
  465. bqc: BigQueryConfig{
  466. ProjectID: "test-project",
  467. BillingDataDataset: "test-dataset.test-table",
  468. Key: map[string]string{},
  469. },
  470. expected: &BigQueryConfiguration{
  471. ProjectID: "test-project",
  472. Dataset: "test-dataset",
  473. Table: "test-table",
  474. Authorizer: &WorkloadIdentity{},
  475. },
  476. },
  477. {
  478. name: "Config with single part dataset",
  479. bqc: BigQueryConfig{
  480. ProjectID: "test-project",
  481. BillingDataDataset: "test-dataset",
  482. Key: map[string]string{},
  483. },
  484. expected: &BigQueryConfiguration{
  485. ProjectID: "test-project",
  486. Dataset: "test-dataset",
  487. Table: "",
  488. Authorizer: &WorkloadIdentity{},
  489. },
  490. },
  491. }
  492. for _, tt := range tests {
  493. t.Run(tt.name, func(t *testing.T) {
  494. result := ConvertBigQueryConfigToConfig(tt.bqc)
  495. if tt.expected == nil {
  496. assert.Nil(t, result)
  497. } else {
  498. assert.NotNil(t, result)
  499. expectedBQC := tt.expected.(*BigQueryConfiguration)
  500. resultBQC := result.(*BigQueryConfiguration)
  501. assert.Equal(t, expectedBQC.ProjectID, resultBQC.ProjectID)
  502. assert.Equal(t, expectedBQC.Dataset, resultBQC.Dataset)
  503. assert.Equal(t, expectedBQC.Table, resultBQC.Table)
  504. assert.NotNil(t, resultBQC.Authorizer)
  505. }
  506. })
  507. }
  508. }
  509. func TestBigQueryConfiguration_UnmarshalJSON_Valid(t *testing.T) {
  510. jsonData := `{
  511. "projectID": "test-project",
  512. "dataset": "test-dataset",
  513. "table": "test-table",
  514. "authorizer": {
  515. "authorizerType": "GCPServiceAccountKey",
  516. "key": {
  517. "type": "service_account"
  518. }
  519. }
  520. }`
  521. var bqc BigQueryConfiguration
  522. err := json.Unmarshal([]byte(jsonData), &bqc)
  523. assert.NoError(t, err)
  524. assert.Equal(t, "test-project", bqc.ProjectID)
  525. assert.Equal(t, "test-dataset", bqc.Dataset)
  526. assert.Equal(t, "test-table", bqc.Table)
  527. assert.NotNil(t, bqc.Authorizer)
  528. saKey, ok := bqc.Authorizer.(*ServiceAccountKey)
  529. assert.True(t, ok)
  530. assert.Equal(t, "service_account", saKey.Key["type"])
  531. }
  532. func TestBigQueryConfiguration_UnmarshalJSON_InvalidProjectID(t *testing.T) {
  533. jsonData := `{
  534. "dataset": "test-dataset",
  535. "table": "test-table",
  536. "authorizer": {
  537. "authorizerType": "GCPServiceAccountKey",
  538. "key": {
  539. "type": "service_account"
  540. }
  541. }
  542. }`
  543. var bqc BigQueryConfiguration
  544. err := json.Unmarshal([]byte(jsonData), &bqc)
  545. assert.Error(t, err)
  546. assert.Contains(t, err.Error(), "projectID")
  547. }
  548. func TestBigQueryConfiguration_UnmarshalJSON_InvalidDataset(t *testing.T) {
  549. jsonData := `{
  550. "projectID": "test-project",
  551. "table": "test-table",
  552. "authorizer": {
  553. "authorizerType": "GCPServiceAccountKey",
  554. "key": {
  555. "type": "service_account"
  556. }
  557. }
  558. }`
  559. var bqc BigQueryConfiguration
  560. err := json.Unmarshal([]byte(jsonData), &bqc)
  561. assert.Error(t, err)
  562. assert.Contains(t, err.Error(), "dataset")
  563. }
  564. func TestBigQueryConfiguration_UnmarshalJSON_InvalidTable(t *testing.T) {
  565. jsonData := `{
  566. "projectID": "test-project",
  567. "dataset": "test-dataset",
  568. "authorizer": {
  569. "authorizerType": "GCPServiceAccountKey",
  570. "key": {
  571. "type": "service_account"
  572. }
  573. }
  574. }`
  575. var bqc BigQueryConfiguration
  576. err := json.Unmarshal([]byte(jsonData), &bqc)
  577. assert.Error(t, err)
  578. assert.Contains(t, err.Error(), "table")
  579. }
  580. func TestBigQueryConfiguration_UnmarshalJSON_MissingAuthorizer(t *testing.T) {
  581. jsonData := `{
  582. "projectID": "test-project",
  583. "dataset": "test-dataset",
  584. "table": "test-table"
  585. }`
  586. var bqc BigQueryConfiguration
  587. err := json.Unmarshal([]byte(jsonData), &bqc)
  588. assert.Error(t, err)
  589. assert.Contains(t, err.Error(), "missing authorizer")
  590. }
  591. func TestBigQueryConfiguration_UnmarshalJSON_InvalidAuthorizer(t *testing.T) {
  592. jsonData := `{
  593. "projectID": "test-project",
  594. "dataset": "test-dataset",
  595. "table": "test-table",
  596. "authorizer": {
  597. "authorizerType": "InvalidType"
  598. }
  599. }`
  600. var bqc BigQueryConfiguration
  601. err := json.Unmarshal([]byte(jsonData), &bqc)
  602. assert.Error(t, err)
  603. assert.Contains(t, err.Error(), "InvalidType")
  604. }