bigqueryconfiguration_test.go 17 KB

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