bigqueryconfiguration_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  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. Location: "EU",
  137. },
  138. right: &BigQueryConfiguration{
  139. ProjectID: "projectID",
  140. Dataset: "dataset",
  141. Table: "table",
  142. Authorizer: &ServiceAccountKey{
  143. Key: map[string]string{
  144. "Key": "Key",
  145. "key1": "key2",
  146. },
  147. },
  148. Location: "EU",
  149. },
  150. expected: true,
  151. },
  152. "different configurer": {
  153. left: BigQueryConfiguration{
  154. ProjectID: "projectID",
  155. Dataset: "dataset",
  156. Table: "table",
  157. Authorizer: &ServiceAccountKey{
  158. Key: map[string]string{
  159. "Key": "Key",
  160. "key1": "key2",
  161. },
  162. },
  163. },
  164. right: &BigQueryConfiguration{
  165. ProjectID: "projectID",
  166. Dataset: "dataset",
  167. Table: "table",
  168. Authorizer: &WorkloadIdentity{},
  169. },
  170. expected: false,
  171. },
  172. "missing both configurer": {
  173. left: BigQueryConfiguration{
  174. ProjectID: "projectID",
  175. Dataset: "dataset",
  176. Table: "table",
  177. Authorizer: nil,
  178. },
  179. right: &BigQueryConfiguration{
  180. ProjectID: "projectID",
  181. Dataset: "dataset",
  182. Table: "table",
  183. Authorizer: nil,
  184. },
  185. expected: true,
  186. },
  187. "missing left configurer": {
  188. left: BigQueryConfiguration{
  189. ProjectID: "projectID",
  190. Dataset: "dataset",
  191. Table: "table",
  192. Authorizer: nil,
  193. },
  194. right: &BigQueryConfiguration{
  195. ProjectID: "projectID",
  196. Dataset: "dataset",
  197. Table: "table",
  198. Authorizer: &WorkloadIdentity{},
  199. },
  200. expected: false,
  201. },
  202. "missing right configurer": {
  203. left: BigQueryConfiguration{
  204. ProjectID: "projectID",
  205. Dataset: "dataset",
  206. Table: "table",
  207. Authorizer: &ServiceAccountKey{
  208. Key: map[string]string{
  209. "Key": "Key",
  210. "key1": "key2",
  211. },
  212. },
  213. },
  214. right: &BigQueryConfiguration{
  215. ProjectID: "projectID",
  216. Dataset: "dataset",
  217. Table: "table",
  218. Authorizer: nil,
  219. },
  220. expected: false,
  221. },
  222. "different projectID": {
  223. left: BigQueryConfiguration{
  224. ProjectID: "projectID",
  225. Dataset: "dataset",
  226. Table: "table",
  227. Authorizer: &ServiceAccountKey{
  228. Key: map[string]string{
  229. "Key": "Key",
  230. "key1": "key2",
  231. },
  232. },
  233. },
  234. right: &BigQueryConfiguration{
  235. ProjectID: "projectID2",
  236. Dataset: "dataset",
  237. Table: "table",
  238. Authorizer: &ServiceAccountKey{
  239. Key: map[string]string{
  240. "Key": "Key",
  241. "key1": "key2",
  242. },
  243. },
  244. },
  245. expected: false,
  246. },
  247. "different dataset": {
  248. left: BigQueryConfiguration{
  249. ProjectID: "projectID",
  250. Dataset: "dataset",
  251. Table: "table",
  252. Authorizer: &ServiceAccountKey{
  253. Key: map[string]string{
  254. "Key": "Key",
  255. "key1": "key2",
  256. },
  257. },
  258. },
  259. right: &BigQueryConfiguration{
  260. ProjectID: "projectID",
  261. Dataset: "dataset2",
  262. Table: "table",
  263. Authorizer: &ServiceAccountKey{
  264. Key: map[string]string{
  265. "Key": "Key",
  266. "key1": "key2",
  267. },
  268. },
  269. },
  270. expected: false,
  271. },
  272. "different table": {
  273. left: BigQueryConfiguration{
  274. ProjectID: "projectID",
  275. Dataset: "dataset",
  276. Table: "table",
  277. Authorizer: &ServiceAccountKey{
  278. Key: map[string]string{
  279. "Key": "Key",
  280. "key1": "key2",
  281. },
  282. },
  283. },
  284. right: &BigQueryConfiguration{
  285. ProjectID: "projectID",
  286. Dataset: "dataset",
  287. Table: "table2",
  288. Authorizer: &ServiceAccountKey{
  289. Key: map[string]string{
  290. "Key": "Key",
  291. "key1": "key2",
  292. },
  293. },
  294. },
  295. expected: false,
  296. },
  297. "different config": {
  298. left: BigQueryConfiguration{
  299. ProjectID: "projectID",
  300. Dataset: "dataset",
  301. Table: "table",
  302. Authorizer: &ServiceAccountKey{
  303. Key: map[string]string{
  304. "Key": "Key",
  305. "key1": "key2",
  306. },
  307. },
  308. },
  309. right: &ServiceAccountKey{
  310. Key: map[string]string{
  311. "Key": "Key",
  312. "key1": "key2",
  313. },
  314. },
  315. expected: false,
  316. },
  317. "different location": {
  318. left: BigQueryConfiguration{
  319. ProjectID: "projectID",
  320. Dataset: "dataset",
  321. Table: "table",
  322. Location: "EU",
  323. Authorizer: &ServiceAccountKey{
  324. Key: map[string]string{
  325. "Key": "Key",
  326. "key1": "key2",
  327. },
  328. },
  329. },
  330. right: &BigQueryConfiguration{
  331. ProjectID: "projectID",
  332. Dataset: "dataset2",
  333. Table: "table",
  334. Location: "US",
  335. Authorizer: &ServiceAccountKey{
  336. Key: map[string]string{
  337. "Key": "Key",
  338. "key1": "key2",
  339. },
  340. },
  341. },
  342. expected: false,
  343. },
  344. }
  345. for name, testCase := range testCases {
  346. t.Run(name, func(t *testing.T) {
  347. actual := testCase.left.Equals(testCase.right)
  348. if actual != testCase.expected {
  349. t.Errorf("incorrect result: Actual: '%t', Expected: '%t", actual, testCase.expected)
  350. }
  351. })
  352. }
  353. }
  354. func TestBigQueryConfiguration_JSON(t *testing.T) {
  355. testCases := map[string]struct {
  356. config BigQueryConfiguration
  357. }{
  358. "Empty Config": {
  359. config: BigQueryConfiguration{},
  360. },
  361. "Nil Authorizer": {
  362. config: BigQueryConfiguration{
  363. ProjectID: "projectID",
  364. Dataset: "dataset",
  365. Table: "table",
  366. Authorizer: nil,
  367. },
  368. },
  369. "ServiceAccountKeyConfigurer": {
  370. config: BigQueryConfiguration{
  371. ProjectID: "projectID",
  372. Dataset: "dataset",
  373. Table: "table",
  374. Authorizer: &ServiceAccountKey{
  375. Key: map[string]string{
  376. "Key": "Key",
  377. "key1": "key2",
  378. },
  379. },
  380. },
  381. },
  382. "WorkLoadIdentityConfigurer": {
  383. config: BigQueryConfiguration{
  384. ProjectID: "projectID",
  385. Dataset: "dataset",
  386. Table: "table",
  387. Authorizer: &WorkloadIdentity{},
  388. },
  389. },
  390. }
  391. for name, testCase := range testCases {
  392. t.Run(name, func(t *testing.T) {
  393. // test JSON Marshalling
  394. configJSON, err := json.Marshal(testCase.config)
  395. if err != nil {
  396. t.Errorf("failed to marshal configuration: %s", err.Error())
  397. }
  398. log.Info(string(configJSON))
  399. unmarshalledConfig := &BigQueryConfiguration{}
  400. err = json.Unmarshal(configJSON, unmarshalledConfig)
  401. if err != nil {
  402. t.Errorf("failed to unmarshal configuration: %s", err.Error())
  403. }
  404. if !testCase.config.Equals(unmarshalledConfig) {
  405. t.Error("config does not equal unmarshalled config")
  406. }
  407. })
  408. }
  409. }
  410. func TestBigQueryConfiguration_Key(t *testing.T) {
  411. bqc := &BigQueryConfiguration{
  412. ProjectID: "test-project",
  413. Dataset: "test-dataset",
  414. Table: "test-table",
  415. }
  416. key := bqc.Key()
  417. expected := "test-project/test-dataset.test-table"
  418. assert.Equal(t, expected, key)
  419. }
  420. func TestBigQueryConfiguration_Provider(t *testing.T) {
  421. bqc := &BigQueryConfiguration{}
  422. provider := bqc.Provider()
  423. assert.Equal(t, "GCP", provider)
  424. }
  425. func TestBigQueryConfiguration_GetBillingDataDataset(t *testing.T) {
  426. bqc := &BigQueryConfiguration{
  427. Dataset: "test-dataset",
  428. Table: "test-table",
  429. }
  430. dataset := bqc.GetBillingDataDataset()
  431. expected := "test-dataset.test-table"
  432. assert.Equal(t, expected, dataset)
  433. }
  434. func TestBigQueryConfiguration_Sanitize(t *testing.T) {
  435. bqc := &BigQueryConfiguration{
  436. ProjectID: "test-project",
  437. Dataset: "test-dataset",
  438. Table: "test-table",
  439. Authorizer: &ServiceAccountKey{
  440. Key: map[string]string{
  441. "type": "service_account",
  442. "private_key": "secret-key",
  443. },
  444. },
  445. }
  446. sanitized := bqc.Sanitize()
  447. require.NotNil(t, sanitized)
  448. sanitizedBQC, ok := sanitized.(*BigQueryConfiguration)
  449. require.True(t, ok)
  450. assert.Equal(t, "test-project", sanitizedBQC.ProjectID)
  451. assert.Equal(t, "test-dataset", sanitizedBQC.Dataset)
  452. assert.Equal(t, "test-table", sanitizedBQC.Table)
  453. assert.NotNil(t, sanitizedBQC.Authorizer)
  454. // Check that the authorizer is also sanitized
  455. saKey, ok := sanitizedBQC.Authorizer.(*ServiceAccountKey)
  456. require.True(t, ok)
  457. for _, value := range saKey.Key {
  458. assert.Equal(t, cloud.Redacted, value)
  459. }
  460. }
  461. func TestConvertBigQueryConfigToConfig(t *testing.T) {
  462. tests := []struct {
  463. name string
  464. bqc BigQueryConfig
  465. expected cloud.KeyedConfig
  466. }{
  467. {
  468. name: "Empty config",
  469. bqc: BigQueryConfig{},
  470. expected: nil,
  471. },
  472. {
  473. name: "Config with service account key",
  474. bqc: BigQueryConfig{
  475. ProjectID: "test-project",
  476. BillingDataDataset: "test-dataset.test-table",
  477. Key: map[string]string{
  478. "type": "service_account",
  479. },
  480. },
  481. expected: &BigQueryConfiguration{
  482. ProjectID: "test-project",
  483. Dataset: "test-dataset",
  484. Table: "test-table",
  485. Authorizer: &ServiceAccountKey{
  486. Key: map[string]string{
  487. "type": "service_account",
  488. },
  489. },
  490. },
  491. },
  492. {
  493. name: "Config without service account key",
  494. bqc: BigQueryConfig{
  495. ProjectID: "test-project",
  496. BillingDataDataset: "test-dataset.test-table",
  497. Key: map[string]string{},
  498. },
  499. expected: &BigQueryConfiguration{
  500. ProjectID: "test-project",
  501. Dataset: "test-dataset",
  502. Table: "test-table",
  503. Authorizer: &WorkloadIdentity{},
  504. },
  505. },
  506. {
  507. name: "Config with single part dataset",
  508. bqc: BigQueryConfig{
  509. ProjectID: "test-project",
  510. BillingDataDataset: "test-dataset",
  511. Key: map[string]string{},
  512. },
  513. expected: &BigQueryConfiguration{
  514. ProjectID: "test-project",
  515. Dataset: "test-dataset",
  516. Table: "",
  517. Authorizer: &WorkloadIdentity{},
  518. },
  519. },
  520. }
  521. for _, tt := range tests {
  522. t.Run(tt.name, func(t *testing.T) {
  523. result := ConvertBigQueryConfigToConfig(tt.bqc)
  524. if tt.expected == nil {
  525. assert.Nil(t, result)
  526. } else {
  527. assert.NotNil(t, result)
  528. expectedBQC := tt.expected.(*BigQueryConfiguration)
  529. resultBQC := result.(*BigQueryConfiguration)
  530. assert.Equal(t, expectedBQC.ProjectID, resultBQC.ProjectID)
  531. assert.Equal(t, expectedBQC.Dataset, resultBQC.Dataset)
  532. assert.Equal(t, expectedBQC.Table, resultBQC.Table)
  533. assert.NotNil(t, resultBQC.Authorizer)
  534. }
  535. })
  536. }
  537. }
  538. func TestBigQueryConfiguration_UnmarshalJSON_Valid(t *testing.T) {
  539. jsonData := `{
  540. "projectID": "test-project",
  541. "dataset": "test-dataset",
  542. "table": "test-table",
  543. "authorizer": {
  544. "authorizerType": "GCPServiceAccountKey",
  545. "key": {
  546. "type": "service_account"
  547. }
  548. }
  549. }`
  550. var bqc BigQueryConfiguration
  551. err := json.Unmarshal([]byte(jsonData), &bqc)
  552. assert.NoError(t, err)
  553. assert.Equal(t, "test-project", bqc.ProjectID)
  554. assert.Equal(t, "test-dataset", bqc.Dataset)
  555. assert.Equal(t, "test-table", bqc.Table)
  556. assert.NotNil(t, bqc.Authorizer)
  557. saKey, ok := bqc.Authorizer.(*ServiceAccountKey)
  558. assert.True(t, ok)
  559. assert.Equal(t, "service_account", saKey.Key["type"])
  560. }
  561. func TestBigQueryConfiguration_UnmarshalJSON_InvalidProjectID(t *testing.T) {
  562. jsonData := `{
  563. "dataset": "test-dataset",
  564. "table": "test-table",
  565. "authorizer": {
  566. "authorizerType": "GCPServiceAccountKey",
  567. "key": {
  568. "type": "service_account"
  569. }
  570. }
  571. }`
  572. var bqc BigQueryConfiguration
  573. err := json.Unmarshal([]byte(jsonData), &bqc)
  574. assert.Error(t, err)
  575. assert.Contains(t, err.Error(), "projectID")
  576. }
  577. func TestBigQueryConfiguration_UnmarshalJSON_InvalidDataset(t *testing.T) {
  578. jsonData := `{
  579. "projectID": "test-project",
  580. "table": "test-table",
  581. "authorizer": {
  582. "authorizerType": "GCPServiceAccountKey",
  583. "key": {
  584. "type": "service_account"
  585. }
  586. }
  587. }`
  588. var bqc BigQueryConfiguration
  589. err := json.Unmarshal([]byte(jsonData), &bqc)
  590. assert.Error(t, err)
  591. assert.Contains(t, err.Error(), "dataset")
  592. }
  593. func TestBigQueryConfiguration_UnmarshalJSON_InvalidTable(t *testing.T) {
  594. jsonData := `{
  595. "projectID": "test-project",
  596. "dataset": "test-dataset",
  597. "authorizer": {
  598. "authorizerType": "GCPServiceAccountKey",
  599. "key": {
  600. "type": "service_account"
  601. }
  602. }
  603. }`
  604. var bqc BigQueryConfiguration
  605. err := json.Unmarshal([]byte(jsonData), &bqc)
  606. assert.Error(t, err)
  607. assert.Contains(t, err.Error(), "table")
  608. }
  609. func TestBigQueryConfiguration_UnmarshalJSON_MissingAuthorizer(t *testing.T) {
  610. jsonData := `{
  611. "projectID": "test-project",
  612. "dataset": "test-dataset",
  613. "table": "test-table"
  614. }`
  615. var bqc BigQueryConfiguration
  616. err := json.Unmarshal([]byte(jsonData), &bqc)
  617. assert.Error(t, err)
  618. assert.Contains(t, err.Error(), "missing authorizer")
  619. }
  620. func TestBigQueryConfiguration_UnmarshalJSON_InvalidAuthorizer(t *testing.T) {
  621. jsonData := `{
  622. "projectID": "test-project",
  623. "dataset": "test-dataset",
  624. "table": "test-table",
  625. "authorizer": {
  626. "authorizerType": "InvalidType"
  627. }
  628. }`
  629. var bqc BigQueryConfiguration
  630. err := json.Unmarshal([]byte(jsonData), &bqc)
  631. assert.Error(t, err)
  632. assert.Contains(t, err.Error(), "InvalidType")
  633. }