bucketstorage_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. package storage
  2. import (
  3. "fmt"
  4. "os"
  5. "path"
  6. "testing"
  7. "github.com/opencost/opencost/core/pkg/util/json"
  8. )
  9. // This suite of integration tests is meant to validate if an implementation of Storage that relies on a could
  10. // bucket service properly implements the interface. To run these tests the env variable "TEST_BUCKET_CONFIG"
  11. // must be set with the path to a valid bucket config as defined in the NewBucketStorage() function.
  12. type testFileContent struct {
  13. Field1 int `json:"field_1"`
  14. Field2 string `json:"field_2"`
  15. }
  16. var tfc = testFileContent{
  17. Field1: 101,
  18. Field2: "TEST_FILE_CONTENT",
  19. }
  20. const testpath = "opencost/storage/"
  21. func createStorage(configPath string) (Storage, error) {
  22. bytes, err := os.ReadFile(configPath)
  23. if err != nil {
  24. return nil, fmt.Errorf("failed to read config file: %w", err)
  25. }
  26. store, err := NewBucketStorage(bytes)
  27. if err != nil {
  28. return nil, fmt.Errorf("failed to initialize storage: %w", err)
  29. }
  30. return store, nil
  31. }
  32. func createFiles(files []string, testName string, store Storage) error {
  33. b, err := json.Marshal(tfc)
  34. if err != nil {
  35. return fmt.Errorf("failed to marshal file content: %w", err)
  36. }
  37. for _, fileName := range files {
  38. filePath := path.Join(testpath, testName, fileName)
  39. err = store.Write(filePath, b)
  40. if err != nil {
  41. return fmt.Errorf("failed to write file '%s': %w ", filePath, err)
  42. }
  43. }
  44. return nil
  45. }
  46. func cleanupFiles(files []string, testName string, store Storage) error {
  47. for _, fileName := range files {
  48. filePath := path.Join(testpath, testName, fileName)
  49. err := store.Remove(filePath)
  50. if err != nil {
  51. return fmt.Errorf("failed to remove file '%s': %w ", filePath, err)
  52. }
  53. }
  54. return nil
  55. }
  56. func TestBucketStorage_List(t *testing.T) {
  57. configPath := os.Getenv("TEST_BUCKET_CONFIG")
  58. if configPath == "" {
  59. t.Skip("skipping integration test, set environment variable TEST_BUCKET_CONFIG")
  60. }
  61. store, err := createStorage(configPath)
  62. if err != nil {
  63. t.Errorf("failed to create storage: %s", err.Error())
  64. return
  65. }
  66. testName := "list"
  67. fileNames := []string{
  68. "/file0.json",
  69. "/file1.json",
  70. "/dir0/file2.json",
  71. "/dir0/file3.json",
  72. }
  73. err = createFiles(fileNames, testName, store)
  74. if err != nil {
  75. t.Errorf("failed to create files: %s", err)
  76. }
  77. defer func() {
  78. err = cleanupFiles(fileNames, testName, store)
  79. if err != nil {
  80. t.Errorf("failed to clean up files: %s", err)
  81. }
  82. }()
  83. testCases := map[string]struct {
  84. path string
  85. expected []string
  86. expectErr bool
  87. }{
  88. "base dir files": {
  89. path: path.Join(testpath, testName),
  90. expected: []string{
  91. "file0.json",
  92. "file1.json",
  93. },
  94. expectErr: false,
  95. },
  96. "single nested dir files": {
  97. path: path.Join(testpath, testName, "dir0"),
  98. expected: []string{
  99. "file2.json",
  100. "file3.json",
  101. },
  102. expectErr: false,
  103. },
  104. "nonexistent dir files": {
  105. path: path.Join(testpath, testName, "dir1"),
  106. expected: []string{},
  107. expectErr: false,
  108. },
  109. }
  110. for name, tc := range testCases {
  111. t.Run(name, func(t *testing.T) {
  112. fileList, err := store.List(tc.path)
  113. if tc.expectErr == (err == nil) {
  114. if tc.expectErr {
  115. t.Errorf("expected error was not thrown")
  116. return
  117. }
  118. t.Errorf("unexpected error: %s", err.Error())
  119. return
  120. }
  121. if len(fileList) != len(tc.expected) {
  122. t.Errorf("file list length does not match expected length, actual: %d, expected: %d", len(fileList), len(tc.expected))
  123. }
  124. expectedSet := map[string]struct{}{}
  125. for _, expName := range tc.expected {
  126. expectedSet[expName] = struct{}{}
  127. }
  128. for _, file := range fileList {
  129. _, ok := expectedSet[file.Name]
  130. if !ok {
  131. t.Errorf("unexpect file in list %s", file.Name)
  132. }
  133. if file.Size == 0 {
  134. t.Errorf("file size is not set")
  135. }
  136. if file.ModTime.IsZero() {
  137. t.Errorf("file mod time is not set")
  138. }
  139. }
  140. })
  141. }
  142. }
  143. func TestBucketStorage_ListDirectories(t *testing.T) {
  144. configPath := os.Getenv("TEST_BUCKET_CONFIG")
  145. if configPath == "" {
  146. t.Skip("skipping integration test, set environment variable TEST_BUCKET_CONFIG")
  147. }
  148. store, err := createStorage(configPath)
  149. if err != nil {
  150. t.Errorf("failed to create storage: %s", err.Error())
  151. return
  152. }
  153. testName := "list_directories"
  154. fileNames := []string{
  155. "/file0.json",
  156. "/dir0/file2.json",
  157. "/dir0/file3.json",
  158. "/dir0/dir1/file4.json",
  159. "/dir0/dir2/file5.json",
  160. }
  161. err = createFiles(fileNames, testName, store)
  162. if err != nil {
  163. t.Errorf("failed to create files: %s", err)
  164. }
  165. defer func() {
  166. err = cleanupFiles(fileNames, testName, store)
  167. if err != nil {
  168. t.Errorf("failed to clean up files: %s", err)
  169. }
  170. }()
  171. testCases := map[string]struct {
  172. path string
  173. expected []string
  174. expectErr bool
  175. }{
  176. "base dir dir": {
  177. path: path.Join(testpath, testName),
  178. expected: []string{
  179. path.Join(testpath, testName, "dir0") + "/",
  180. },
  181. expectErr: false,
  182. },
  183. "single nested dir files": {
  184. path: path.Join(testpath, testName, "dir0"),
  185. expected: []string{
  186. path.Join(testpath, testName, "dir0", "dir1") + "/",
  187. path.Join(testpath, testName, "dir0", "dir2") + "/",
  188. },
  189. expectErr: false,
  190. },
  191. "dir with no sub dirs": {
  192. path: path.Join(testpath, testName, "dir0/dir1"),
  193. expected: []string{},
  194. expectErr: false,
  195. },
  196. "non-existent dir": {
  197. path: path.Join(testpath, testName, "dir1"),
  198. expected: []string{},
  199. expectErr: false,
  200. },
  201. }
  202. for name, tc := range testCases {
  203. t.Run(name, func(t *testing.T) {
  204. dirList, err := store.ListDirectories(tc.path)
  205. if tc.expectErr == (err == nil) {
  206. if tc.expectErr {
  207. t.Errorf("expected error was not thrown")
  208. return
  209. }
  210. t.Errorf("unexpected error: %s", err.Error())
  211. return
  212. }
  213. if len(dirList) != len(tc.expected) {
  214. t.Errorf("dir list length does not match expected length, actual: %d, expected: %d", len(dirList), len(tc.expected))
  215. }
  216. expectedSet := map[string]struct{}{}
  217. for _, expName := range tc.expected {
  218. expectedSet[expName] = struct{}{}
  219. }
  220. for _, dir := range dirList {
  221. _, ok := expectedSet[dir.Name]
  222. if !ok {
  223. t.Errorf("unexpect dir in list %s", dir.Name)
  224. }
  225. }
  226. })
  227. }
  228. }
  229. func TestBucketStorage_Exists(t *testing.T) {
  230. configPath := os.Getenv("TEST_BUCKET_CONFIG")
  231. if configPath == "" {
  232. t.Skip("skipping integration test, set environment variable TEST_BUCKET_CONFIG")
  233. }
  234. store, err := createStorage(configPath)
  235. if err != nil {
  236. t.Errorf("failed to create storage: %s", err.Error())
  237. return
  238. }
  239. testName := "exists"
  240. fileNames := []string{
  241. "/file0.json",
  242. }
  243. err = createFiles(fileNames, testName, store)
  244. if err != nil {
  245. t.Errorf("failed to create files: %s", err)
  246. }
  247. defer func() {
  248. err = cleanupFiles(fileNames, testName, store)
  249. if err != nil {
  250. t.Errorf("failed to clean up files: %s", err)
  251. }
  252. }()
  253. testCases := map[string]struct {
  254. path string
  255. expected bool
  256. expectErr bool
  257. }{
  258. "file exists": {
  259. path: path.Join(testpath, testName, "file0.json"),
  260. expected: true,
  261. expectErr: false,
  262. },
  263. "file does not exist": {
  264. path: path.Join(testpath, testName, "file1.json"),
  265. expected: false,
  266. expectErr: false,
  267. },
  268. "dir does not exist": {
  269. path: path.Join(testpath, testName, "dir0/file.json"),
  270. expected: false,
  271. expectErr: false,
  272. },
  273. }
  274. for name, tc := range testCases {
  275. t.Run(name, func(t *testing.T) {
  276. exists, err := store.Exists(tc.path)
  277. if tc.expectErr == (err == nil) {
  278. if tc.expectErr {
  279. t.Errorf("expected error was not thrown")
  280. return
  281. }
  282. t.Errorf("unexpected error: %s", err.Error())
  283. return
  284. }
  285. if exists != tc.expected {
  286. t.Errorf("file exists output did not match expected")
  287. }
  288. })
  289. }
  290. }
  291. func TestBucketStorage_Read(t *testing.T) {
  292. configPath := os.Getenv("TEST_BUCKET_CONFIG")
  293. if configPath == "" {
  294. t.Skip("skipping integration test, set environment variable TEST_BUCKET_CONFIG")
  295. }
  296. store, err := createStorage(configPath)
  297. if err != nil {
  298. t.Errorf("failed to create storage: %s", err.Error())
  299. return
  300. }
  301. testName := "read"
  302. fileNames := []string{
  303. "/file0.json",
  304. }
  305. err = createFiles(fileNames, testName, store)
  306. if err != nil {
  307. t.Errorf("failed to create files: %s", err)
  308. }
  309. defer func() {
  310. err = cleanupFiles(fileNames, testName, store)
  311. if err != nil {
  312. t.Errorf("failed to clean up files: %s", err)
  313. }
  314. }()
  315. testCases := map[string]struct {
  316. path string
  317. expectErr bool
  318. }{
  319. "file exists": {
  320. path: path.Join(testpath, testName, "file0.json"),
  321. expectErr: false,
  322. },
  323. "file does not exist": {
  324. path: path.Join(testpath, testName, "file1.json"),
  325. expectErr: true,
  326. },
  327. "dir does not exist": {
  328. path: path.Join(testpath, testName, "dir0/file.json"),
  329. expectErr: true,
  330. },
  331. }
  332. for name, tc := range testCases {
  333. t.Run(name, func(t *testing.T) {
  334. b, err := store.Read(tc.path)
  335. if tc.expectErr && err != nil {
  336. return
  337. }
  338. if tc.expectErr == (err == nil) {
  339. if tc.expectErr {
  340. t.Errorf("expected error was not thrown")
  341. return
  342. }
  343. t.Errorf("unexpected error: %s", err.Error())
  344. return
  345. }
  346. var content testFileContent
  347. err = json.Unmarshal(b, &content)
  348. if err != nil {
  349. t.Errorf("could not unmarshal file content")
  350. return
  351. }
  352. if content != tfc {
  353. t.Errorf("file content did not match writen value")
  354. }
  355. })
  356. }
  357. }
  358. func TestBucketStorage_Stat(t *testing.T) {
  359. configPath := os.Getenv("TEST_BUCKET_CONFIG")
  360. if configPath == "" {
  361. t.Skip("skipping integration test, set environment variable TEST_BUCKET_CONFIG")
  362. }
  363. store, err := createStorage(configPath)
  364. if err != nil {
  365. t.Errorf("failed to create storage: %s", err.Error())
  366. return
  367. }
  368. testName := "stat"
  369. fileNames := []string{
  370. "/file0.json",
  371. }
  372. err = createFiles(fileNames, testName, store)
  373. if err != nil {
  374. t.Errorf("failed to create files: %s", err)
  375. }
  376. defer func() {
  377. err = cleanupFiles(fileNames, testName, store)
  378. if err != nil {
  379. t.Errorf("failed to clean up files: %s", err)
  380. }
  381. }()
  382. testCases := map[string]struct {
  383. path string
  384. expected *StorageInfo
  385. expectErr bool
  386. }{
  387. "base dir": {
  388. path: path.Join(testpath, testName, "file0.json"),
  389. expected: &StorageInfo{
  390. Name: "file0.json",
  391. Size: 45,
  392. },
  393. expectErr: false,
  394. },
  395. "file does not exist": {
  396. path: path.Join(testpath, testName, "file1.json"),
  397. expected: nil,
  398. expectErr: true,
  399. },
  400. }
  401. for name, tc := range testCases {
  402. t.Run(name, func(t *testing.T) {
  403. status, err := store.Stat(tc.path)
  404. if tc.expectErr && err != nil {
  405. return
  406. }
  407. if tc.expectErr == (err == nil) {
  408. if tc.expectErr {
  409. t.Errorf("expected error was not thrown")
  410. return
  411. }
  412. t.Errorf("unexpected error: %s", err.Error())
  413. return
  414. }
  415. if status.Name != tc.expected.Name {
  416. t.Errorf("status name did name match expected, actual: %s, expected: %s", status.Name, tc.expected.Name)
  417. }
  418. if status.Size != tc.expected.Size {
  419. t.Errorf("status name did size match expected, actual: %d, expected: %d", status.Size, tc.expected.Size)
  420. }
  421. if status.ModTime.IsZero() {
  422. t.Errorf("status mod time is not set")
  423. }
  424. })
  425. }
  426. }