onepassword_test.go 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package onepassword
  14. import (
  15. "context"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "reflect"
  20. "testing"
  21. "github.com/1Password/connect-sdk-go/onepassword"
  22. corev1 "k8s.io/api/core/v1"
  23. apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  27. "github.com/external-secrets/external-secrets/providers/v1/onepassword/fake"
  28. "github.com/external-secrets/external-secrets/runtime/esutils/metadata"
  29. )
  30. const (
  31. // vaults and items.
  32. myVault, myVaultID = "my-vault", "my-vault-id"
  33. myItem, myItemID = "my-item", "my-item-id"
  34. myNativeItemID = "gdpvdudxrico74msloimk7qjna"
  35. mySharedVault, mySharedVaultID = "my-shared-vault", "my-shared-vault-id"
  36. mySharedItem, mySharedItemID = "my-shared-item", "my-shared-item-id"
  37. myOtherVault, myOtherVaultID = "my-other-vault", "my-other-vault-id"
  38. myOtherItem, myOtherItemID = "my-other-item", "my-other-item-id"
  39. myNonMatchingVault, myNonMatchingVaultID = "my-non-matching-vault", "my-non-matching-vault-id"
  40. myNonMatchingItem, myNonMatchingItemID = "my-non-matching-item", "my-non-matching-item-id"
  41. // fields and files.
  42. key1, key2, key3, key4 = "key1", "key2", "key3", "key4"
  43. value1, value2, value3, value4 = "value1", "value2", "value3", "value4"
  44. sharedKey1, sharedValue1 = "sharedkey1", "sharedvalue1"
  45. otherKey1 = "otherkey1"
  46. filePNG, filePNGID = "file.png", "file-id"
  47. myFilePNG, myFilePNGID, myContents = "my-file.png", "my-file-id", "my-contents"
  48. mySecondFileTXT, mySecondFileTXTID = "my-second-file.txt", "my-second-file-id"
  49. mySecondContents = "my-second-contents"
  50. myFile2PNG, myFile2TXT = "my-file-2.png", "my-file-2.txt"
  51. myFile2ID, myContents2 = "my-file-2-id", "my-contents-2"
  52. myOtherFilePNG, myOtherFilePNGID = "my-other-file.png", "my-other-file-id"
  53. myOtherContents = "my-other-contents"
  54. nonMatchingFilePNG, nonMatchingFilePNGID = "non-matching-file.png", "non-matching-file-id"
  55. nonMatchingContents = "non-matching-contents"
  56. // other.
  57. mySecret, token, password = "my-secret", "token", "password"
  58. one, two, three = "one", "two", "three"
  59. connectHost = "https://example.com"
  60. setupCheckFormat = "Setup: '%s', Check: '%s'"
  61. getSecretMapErrFormat = "%s: onepassword.GetSecretMap(...): -expected, +got:\n-%#v\n+%#v\n"
  62. getSecretErrFormat = "%s: onepassword.GetSecret(...): -expected, +got:\n-%#v\n+%#v\n"
  63. getAllSecretsErrFormat = "%s: onepassword.GetAllSecrets(...): -expected, +got:\n-%#v\n+%#v\n"
  64. validateStoreErrFormat = "%s: onepassword.validateStore(...): -expected, +got:\n-%#v\n+%#v\n"
  65. findItemErrFormat = "%s: onepassword.findItem(...): -expected, +got:\n-%#v\n+%#v\n"
  66. errFromErrMsgF = "%w: %s"
  67. errDoesNotMatchMsgF = "%s: error did not match: -expected, +got:\\n-%#v\\n+%#v\\n"
  68. )
  69. func TestFindItem(t *testing.T) {
  70. type check struct {
  71. checkNote string
  72. findItemName string
  73. expectedItem *onepassword.Item
  74. expectedErr error
  75. }
  76. type testCase struct {
  77. setupNote string
  78. provider *ProviderOnePassword
  79. checks []check
  80. }
  81. testCases := []testCase{
  82. {
  83. setupNote: "valid basic: one vault, one item, one field",
  84. provider: &ProviderOnePassword{
  85. vaults: map[string]int{myVault: 1},
  86. client: fake.NewMockClient().
  87. AddPredictableVault(myVault).
  88. AddPredictableItemWithField(myVault, myItem, key1, value1),
  89. },
  90. checks: []check{
  91. {
  92. checkNote: "pass",
  93. findItemName: myItem,
  94. expectedErr: nil,
  95. expectedItem: &onepassword.Item{
  96. ID: myItemID,
  97. Title: myItem,
  98. Vault: onepassword.ItemVault{ID: myVaultID},
  99. Fields: []*onepassword.ItemField{
  100. {
  101. Label: key1,
  102. Value: value1,
  103. },
  104. },
  105. },
  106. },
  107. },
  108. },
  109. {
  110. setupNote: "native item ID: one vault, one item, one field",
  111. provider: &ProviderOnePassword{
  112. vaults: map[string]int{myVault: 1},
  113. client: fake.NewMockClient().
  114. AddPredictableVault(myVault).
  115. AppendItem(myVaultID, onepassword.Item{
  116. ID: myNativeItemID,
  117. Title: "My App (Production)",
  118. Vault: onepassword.ItemVault{ID: myVaultID},
  119. }).
  120. AppendItemField(myVaultID, myNativeItemID, onepassword.ItemField{
  121. Label: key1,
  122. Value: value1,
  123. }),
  124. },
  125. checks: []check{
  126. {
  127. checkNote: "find by native item ID",
  128. findItemName: myNativeItemID,
  129. expectedErr: nil,
  130. expectedItem: &onepassword.Item{
  131. ID: myNativeItemID,
  132. Title: "My App (Production)",
  133. Vault: onepassword.ItemVault{ID: myVaultID},
  134. Fields: []*onepassword.ItemField{
  135. {
  136. Label: key1,
  137. Value: value1,
  138. },
  139. },
  140. },
  141. },
  142. },
  143. },
  144. {
  145. setupNote: "multiple vaults, multiple items",
  146. provider: &ProviderOnePassword{
  147. vaults: map[string]int{myVault: 1, mySharedVault: 2},
  148. client: fake.NewMockClient().
  149. AddPredictableVault(myVault).
  150. AddPredictableItemWithField(myVault, myItem, key1, value1).
  151. AddPredictableVault(mySharedVault).
  152. AddPredictableItemWithField(mySharedVault, mySharedItem, sharedKey1, sharedValue1),
  153. },
  154. checks: []check{
  155. {
  156. checkNote: "can still get myItem",
  157. findItemName: myItem,
  158. expectedErr: nil,
  159. expectedItem: &onepassword.Item{
  160. ID: myItemID,
  161. Title: myItem,
  162. Vault: onepassword.ItemVault{ID: myVaultID},
  163. Fields: []*onepassword.ItemField{
  164. {
  165. Label: key1,
  166. Value: value1,
  167. },
  168. },
  169. },
  170. },
  171. {
  172. checkNote: "can also get mySharedItem",
  173. findItemName: mySharedItem,
  174. expectedErr: nil,
  175. expectedItem: &onepassword.Item{
  176. ID: mySharedItemID,
  177. Title: mySharedItem,
  178. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  179. Fields: []*onepassword.ItemField{
  180. {
  181. Label: sharedKey1,
  182. Value: sharedValue1,
  183. },
  184. },
  185. },
  186. },
  187. },
  188. },
  189. {
  190. setupNote: "multiple vault matches when should be one",
  191. provider: &ProviderOnePassword{
  192. vaults: map[string]int{myVault: 1, mySharedVault: 2},
  193. client: fake.NewMockClient().
  194. AppendVault(myVault, onepassword.Vault{
  195. ID: myVaultID,
  196. Name: myVault,
  197. }).
  198. AppendVault(myVault, onepassword.Vault{
  199. ID: "my-vault-extra-match-id",
  200. Name: "my-vault-extra-match",
  201. }),
  202. },
  203. checks: []check{
  204. {
  205. checkNote: "two vaults",
  206. findItemName: myItem,
  207. expectedErr: errors.New("key not found in 1Password Vaults: my-item in: map[my-shared-vault:2 my-vault:1]"),
  208. },
  209. },
  210. },
  211. {
  212. setupNote: "no item matches when should be one",
  213. provider: &ProviderOnePassword{
  214. vaults: map[string]int{myVault: 1},
  215. client: fake.NewMockClient().
  216. AddPredictableVault(myVault),
  217. },
  218. checks: []check{
  219. {
  220. checkNote: "no exist",
  221. findItemName: "my-item-no-exist",
  222. expectedErr: fmt.Errorf("%w: my-item-no-exist in: map[my-vault:1]", ErrKeyNotFound),
  223. },
  224. },
  225. },
  226. {
  227. setupNote: "multiple item matches when should be one",
  228. provider: &ProviderOnePassword{
  229. vaults: map[string]int{myVault: 1},
  230. client: fake.NewMockClient().
  231. AddPredictableVault(myVault).
  232. AddPredictableItemWithField(myVault, myItem, key1, value1).
  233. AppendItem(myVaultID, onepassword.Item{
  234. ID: "asdf",
  235. Title: myItem,
  236. Vault: onepassword.ItemVault{ID: myVaultID},
  237. }),
  238. },
  239. checks: []check{
  240. {
  241. checkNote: "multiple match",
  242. findItemName: myItem,
  243. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneItem, "'my-item', got 2"),
  244. },
  245. },
  246. },
  247. {
  248. setupNote: "ordered vaults",
  249. provider: &ProviderOnePassword{
  250. vaults: map[string]int{myVault: 1, mySharedVault: 2, myOtherVault: 3},
  251. client: fake.NewMockClient().
  252. AddPredictableVault(myVault).
  253. AddPredictableVault(mySharedVault).
  254. AddPredictableVault(myOtherVault).
  255. // // my-item
  256. // returned: my-item in my-vault
  257. AddPredictableItemWithField(myVault, myItem, key1, value1).
  258. // preempted: my-item in my-shared-vault
  259. AppendItem(mySharedVaultID, onepassword.Item{
  260. ID: myItemID,
  261. Title: myItem,
  262. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  263. }).
  264. AppendItemField(mySharedVaultID, myItemID, onepassword.ItemField{
  265. Label: key1,
  266. Value: "value1-from-my-shared-vault",
  267. }).
  268. // preempted: my-item in my-other-vault
  269. AppendItem(myOtherVaultID, onepassword.Item{
  270. ID: myItemID,
  271. Title: myItem,
  272. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  273. }).
  274. AppendItemField(myOtherVaultID, myItemID, onepassword.ItemField{
  275. Label: key1,
  276. Value: "value1-from-my-other-vault",
  277. }).
  278. // // my-shared-item
  279. // returned: my-shared-item in my-shared-vault
  280. AddPredictableItemWithField(mySharedVault, mySharedItem, sharedKey1, "sharedvalue1-from-my-shared-vault").
  281. // preempted: my-shared-item in my-other-vault
  282. AppendItem(myOtherVaultID, onepassword.Item{
  283. ID: mySharedItemID,
  284. Title: mySharedItem,
  285. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  286. }).
  287. AppendItemField(myOtherVaultID, mySharedItemID, onepassword.ItemField{
  288. Label: sharedKey1,
  289. Value: "sharedvalue1-from-my-other-vault",
  290. }).
  291. // // my-other-item
  292. // returned: my-other-item in my-other-vault
  293. AddPredictableItemWithField(myOtherVault, myOtherItem, otherKey1, "othervalue1-from-my-other-vault"),
  294. },
  295. checks: []check{
  296. {
  297. // my-item in all three vaults, gets the one from my-vault
  298. checkNote: "gets item from my-vault",
  299. findItemName: myItem,
  300. expectedErr: nil,
  301. expectedItem: &onepassword.Item{
  302. ID: myItemID,
  303. Title: myItem,
  304. Vault: onepassword.ItemVault{ID: myVaultID},
  305. Fields: []*onepassword.ItemField{
  306. {
  307. Label: key1,
  308. Value: value1,
  309. },
  310. },
  311. },
  312. },
  313. {
  314. // my-shared-item in my-shared-vault and my-other-vault, gets the one from my-shared-vault
  315. checkNote: "gets item from my-shared-vault",
  316. findItemName: mySharedItem,
  317. expectedErr: nil,
  318. expectedItem: &onepassword.Item{
  319. ID: mySharedItemID,
  320. Title: mySharedItem,
  321. Vault: onepassword.ItemVault{ID: mySharedVaultID},
  322. Fields: []*onepassword.ItemField{
  323. {
  324. Label: sharedKey1,
  325. Value: "sharedvalue1-from-my-shared-vault",
  326. },
  327. },
  328. },
  329. },
  330. {
  331. // my-other-item in my-other-vault
  332. checkNote: "gets item from my-other-vault",
  333. findItemName: myOtherItem,
  334. expectedErr: nil,
  335. expectedItem: &onepassword.Item{
  336. ID: myOtherItemID,
  337. Title: myOtherItem,
  338. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  339. Fields: []*onepassword.ItemField{
  340. {
  341. Label: otherKey1,
  342. Value: "othervalue1-from-my-other-vault",
  343. },
  344. },
  345. },
  346. },
  347. },
  348. },
  349. }
  350. // run the tests
  351. for num, tc := range testCases {
  352. t.Run(fmt.Sprintf("test-%d", num), func(t *testing.T) {
  353. for _, check := range tc.checks {
  354. got, err := tc.provider.findItem(check.findItemName)
  355. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  356. if check.expectedErr == nil && err != nil {
  357. // expected no error, got one
  358. t.Errorf(findItemErrFormat, notes, nil, err)
  359. }
  360. if check.expectedErr != nil && err == nil {
  361. // expected an error, didn't get one
  362. t.Errorf(findItemErrFormat, notes, check.expectedErr.Error(), nil)
  363. }
  364. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  365. // expected an error, got the wrong one
  366. t.Errorf(findItemErrFormat, notes, check.expectedErr.Error(), err.Error())
  367. }
  368. if check.expectedItem != nil {
  369. if !reflect.DeepEqual(check.expectedItem, got) {
  370. // expected a predefined item, got something else
  371. t.Errorf(findItemErrFormat, notes, check.expectedItem, got)
  372. }
  373. }
  374. }
  375. })
  376. }
  377. }
  378. func TestValidateStore(t *testing.T) {
  379. type testCase struct {
  380. checkNote string
  381. store *esv1.SecretStore
  382. clusterStore *esv1.ClusterSecretStore
  383. expectedErr error
  384. }
  385. testCases := []testCase{
  386. {
  387. checkNote: "invalid: nil provider",
  388. store: &esv1.SecretStore{
  389. TypeMeta: metav1.TypeMeta{
  390. Kind: "SecretStore",
  391. },
  392. Spec: esv1.SecretStoreSpec{
  393. Provider: nil,
  394. },
  395. },
  396. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNilSpecProvider)),
  397. },
  398. {
  399. checkNote: "invalid: nil OnePassword provider spec",
  400. store: &esv1.SecretStore{
  401. TypeMeta: metav1.TypeMeta{
  402. Kind: "SecretStore",
  403. },
  404. Spec: esv1.SecretStoreSpec{
  405. Provider: &esv1.SecretStoreProvider{
  406. OnePassword: nil,
  407. },
  408. },
  409. },
  410. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNilSpecProviderOnePassword)),
  411. },
  412. {
  413. checkNote: "valid secretStore",
  414. store: &esv1.SecretStore{
  415. TypeMeta: metav1.TypeMeta{
  416. Kind: "SecretStore",
  417. },
  418. Spec: esv1.SecretStoreSpec{
  419. Provider: &esv1.SecretStoreProvider{
  420. OnePassword: &esv1.OnePasswordProvider{
  421. Auth: &esv1.OnePasswordAuth{
  422. SecretRef: &esv1.OnePasswordAuthSecretRef{
  423. ConnectToken: esmeta.SecretKeySelector{
  424. Name: mySecret,
  425. Key: token,
  426. },
  427. },
  428. },
  429. ConnectHost: connectHost,
  430. Vaults: map[string]int{
  431. myVault: 1,
  432. },
  433. },
  434. },
  435. },
  436. },
  437. expectedErr: nil,
  438. },
  439. {
  440. checkNote: "invalid: illegal namespace on SecretStore",
  441. store: &esv1.SecretStore{
  442. TypeMeta: metav1.TypeMeta{
  443. Kind: "SecretStore",
  444. },
  445. Spec: esv1.SecretStoreSpec{
  446. Provider: &esv1.SecretStoreProvider{
  447. OnePassword: &esv1.OnePasswordProvider{
  448. Auth: &esv1.OnePasswordAuth{
  449. SecretRef: &esv1.OnePasswordAuthSecretRef{
  450. ConnectToken: esmeta.SecretKeySelector{
  451. Name: mySecret,
  452. Namespace: new("my-namespace"),
  453. Key: token,
  454. },
  455. },
  456. },
  457. ConnectHost: connectHost,
  458. Vaults: map[string]int{
  459. myVault: 1,
  460. myOtherVault: 2,
  461. },
  462. },
  463. },
  464. },
  465. },
  466. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New("namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore")),
  467. },
  468. {
  469. checkNote: "invalid: more than one vault with the same number",
  470. store: &esv1.SecretStore{
  471. TypeMeta: metav1.TypeMeta{
  472. Kind: "SecretStore",
  473. },
  474. Spec: esv1.SecretStoreSpec{
  475. Provider: &esv1.SecretStoreProvider{
  476. OnePassword: &esv1.OnePasswordProvider{
  477. Auth: &esv1.OnePasswordAuth{
  478. SecretRef: &esv1.OnePasswordAuthSecretRef{
  479. ConnectToken: esmeta.SecretKeySelector{
  480. Name: mySecret,
  481. Key: token,
  482. },
  483. },
  484. },
  485. ConnectHost: connectHost,
  486. Vaults: map[string]int{
  487. myVault: 1,
  488. myOtherVault: 1,
  489. },
  490. },
  491. },
  492. },
  493. },
  494. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreNonUniqueVaultNumbers)),
  495. },
  496. {
  497. checkNote: "valid: clusterSecretStore",
  498. clusterStore: &esv1.ClusterSecretStore{
  499. TypeMeta: metav1.TypeMeta{
  500. Kind: "ClusterSecretStore",
  501. },
  502. Spec: esv1.SecretStoreSpec{
  503. Provider: &esv1.SecretStoreProvider{
  504. OnePassword: &esv1.OnePasswordProvider{
  505. Auth: &esv1.OnePasswordAuth{
  506. SecretRef: &esv1.OnePasswordAuthSecretRef{
  507. ConnectToken: esmeta.SecretKeySelector{
  508. Name: mySecret,
  509. Namespace: new("my-namespace"),
  510. Key: token,
  511. },
  512. },
  513. },
  514. ConnectHost: connectHost,
  515. Vaults: map[string]int{
  516. myVault: 1,
  517. },
  518. },
  519. },
  520. },
  521. },
  522. expectedErr: nil,
  523. },
  524. {
  525. checkNote: "invalid: clusterSecretStore without namespace",
  526. clusterStore: &esv1.ClusterSecretStore{
  527. TypeMeta: metav1.TypeMeta{
  528. Kind: "ClusterSecretStore",
  529. },
  530. Spec: esv1.SecretStoreSpec{
  531. Provider: &esv1.SecretStoreProvider{
  532. OnePassword: &esv1.OnePasswordProvider{
  533. Auth: &esv1.OnePasswordAuth{
  534. SecretRef: &esv1.OnePasswordAuthSecretRef{
  535. ConnectToken: esmeta.SecretKeySelector{
  536. Name: mySecret,
  537. Key: token,
  538. },
  539. },
  540. },
  541. ConnectHost: connectHost,
  542. Vaults: map[string]int{
  543. myVault: 1,
  544. myOtherVault: 2,
  545. },
  546. },
  547. },
  548. },
  549. },
  550. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New("cluster scope requires namespace")),
  551. },
  552. {
  553. checkNote: "invalid: missing connectTokenSecretRef.name",
  554. store: &esv1.SecretStore{
  555. TypeMeta: metav1.TypeMeta{
  556. Kind: "SecretStore",
  557. },
  558. Spec: esv1.SecretStoreSpec{
  559. Provider: &esv1.SecretStoreProvider{
  560. OnePassword: &esv1.OnePasswordProvider{
  561. Auth: &esv1.OnePasswordAuth{
  562. SecretRef: &esv1.OnePasswordAuthSecretRef{
  563. ConnectToken: esmeta.SecretKeySelector{
  564. Key: token,
  565. },
  566. },
  567. },
  568. ConnectHost: connectHost,
  569. Vaults: map[string]int{
  570. myVault: 1,
  571. myOtherVault: 2,
  572. },
  573. },
  574. },
  575. },
  576. },
  577. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreMissingRefName)),
  578. },
  579. {
  580. checkNote: "invalid: missing connectTokenSecretRef.key",
  581. store: &esv1.SecretStore{
  582. TypeMeta: metav1.TypeMeta{
  583. Kind: "SecretStore",
  584. },
  585. Spec: esv1.SecretStoreSpec{
  586. Provider: &esv1.SecretStoreProvider{
  587. OnePassword: &esv1.OnePasswordProvider{
  588. Auth: &esv1.OnePasswordAuth{
  589. SecretRef: &esv1.OnePasswordAuthSecretRef{
  590. ConnectToken: esmeta.SecretKeySelector{
  591. Name: mySecret,
  592. },
  593. },
  594. },
  595. ConnectHost: connectHost,
  596. Vaults: map[string]int{
  597. myVault: 1,
  598. myOtherVault: 2,
  599. },
  600. },
  601. },
  602. },
  603. },
  604. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreMissingRefKey)),
  605. },
  606. {
  607. checkNote: "invalid: at least one vault",
  608. store: &esv1.SecretStore{
  609. TypeMeta: metav1.TypeMeta{
  610. Kind: "SecretStore",
  611. },
  612. Spec: esv1.SecretStoreSpec{
  613. Provider: &esv1.SecretStoreProvider{
  614. OnePassword: &esv1.OnePasswordProvider{
  615. Auth: &esv1.OnePasswordAuth{
  616. SecretRef: &esv1.OnePasswordAuthSecretRef{
  617. ConnectToken: esmeta.SecretKeySelector{
  618. Name: mySecret,
  619. Key: token,
  620. },
  621. },
  622. },
  623. ConnectHost: connectHost,
  624. Vaults: map[string]int{},
  625. },
  626. },
  627. },
  628. },
  629. expectedErr: fmt.Errorf(errOnePasswordStore, errors.New(errOnePasswordStoreAtLeastOneVault)),
  630. },
  631. {
  632. checkNote: "invalid: url",
  633. store: &esv1.SecretStore{
  634. TypeMeta: metav1.TypeMeta{
  635. Kind: "SecretStore",
  636. },
  637. Spec: esv1.SecretStoreSpec{
  638. Provider: &esv1.SecretStoreProvider{
  639. OnePassword: &esv1.OnePasswordProvider{
  640. Auth: &esv1.OnePasswordAuth{
  641. SecretRef: &esv1.OnePasswordAuthSecretRef{
  642. ConnectToken: esmeta.SecretKeySelector{
  643. Name: mySecret,
  644. Key: token,
  645. },
  646. },
  647. },
  648. ConnectHost: ":/invalid.invalid",
  649. Vaults: map[string]int{
  650. myVault: 1,
  651. },
  652. },
  653. },
  654. },
  655. },
  656. expectedErr: fmt.Errorf(errOnePasswordStore, fmt.Errorf(errOnePasswordStoreInvalidConnectHost, errors.New("parse \":/invalid.invalid\": missing protocol scheme"))),
  657. },
  658. }
  659. // run the tests
  660. for _, tc := range testCases {
  661. var err error
  662. if tc.store == nil {
  663. err = validateStore(tc.clusterStore)
  664. } else {
  665. err = validateStore(tc.store)
  666. }
  667. notes := fmt.Sprintf("Check: '%s'", tc.checkNote)
  668. if tc.expectedErr == nil && err != nil {
  669. // expected no error, got one
  670. t.Errorf(validateStoreErrFormat, notes, nil, err)
  671. }
  672. if tc.expectedErr != nil && err == nil {
  673. // expected an error, didn't get one
  674. t.Errorf(validateStoreErrFormat, notes, tc.expectedErr.Error(), nil)
  675. }
  676. if tc.expectedErr != nil && err != nil && err.Error() != tc.expectedErr.Error() {
  677. // expected an error, got the wrong one
  678. t.Errorf(validateStoreErrFormat, notes, tc.expectedErr.Error(), err.Error())
  679. }
  680. }
  681. }
  682. // most functionality is tested in TestFindItem
  683. //
  684. // here we just check that an empty Property defaults to "password",
  685. // files are loaded, and
  686. // the data or errors are properly returned
  687. func TestGetSecret(t *testing.T) {
  688. type check struct {
  689. checkNote string
  690. ref esv1.ExternalSecretDataRemoteRef
  691. expectedValue string
  692. expectedErr error
  693. }
  694. type testCase struct {
  695. setupNote string
  696. provider *ProviderOnePassword
  697. checks []check
  698. }
  699. testCases := []testCase{
  700. {
  701. setupNote: "one vault, one item, two fields",
  702. provider: &ProviderOnePassword{
  703. vaults: map[string]int{myVault: 1},
  704. client: fake.NewMockClient().
  705. AddPredictableVault(myVault).
  706. AppendItem(myVaultID, onepassword.Item{
  707. ID: myItemID,
  708. Title: myItem,
  709. Vault: onepassword.ItemVault{ID: myVaultID},
  710. Files: []*onepassword.File{
  711. {
  712. ID: myFilePNGID,
  713. Name: myFilePNG,
  714. },
  715. },
  716. }).
  717. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  718. Label: password,
  719. Value: value2,
  720. }).
  721. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  722. Label: key1,
  723. Value: value1,
  724. }).
  725. SetFileContents(myFilePNG, []byte(myContents)),
  726. },
  727. checks: []check{
  728. {
  729. checkNote: key1,
  730. ref: esv1.ExternalSecretDataRemoteRef{
  731. Key: myItem,
  732. Property: key1,
  733. },
  734. expectedValue: value1,
  735. expectedErr: nil,
  736. },
  737. {
  738. checkNote: key1 + " with prefix",
  739. ref: esv1.ExternalSecretDataRemoteRef{
  740. Key: myItem,
  741. Property: fieldPrefix + prefixSplitter + key1,
  742. },
  743. expectedValue: value1,
  744. expectedErr: nil,
  745. },
  746. {
  747. checkNote: "'password' (defaulted property)",
  748. ref: esv1.ExternalSecretDataRemoteRef{
  749. Key: myItem,
  750. },
  751. expectedValue: value2,
  752. expectedErr: nil,
  753. },
  754. {
  755. checkNote: "'ref.version' not implemented",
  756. ref: esv1.ExternalSecretDataRemoteRef{
  757. Key: myItem,
  758. Property: key1,
  759. Version: "123",
  760. },
  761. expectedErr: errors.New(errVersionNotImplemented),
  762. },
  763. {
  764. checkNote: "file named my-file.png with prefix",
  765. ref: esv1.ExternalSecretDataRemoteRef{
  766. Key: myItem,
  767. Property: filePrefix + prefixSplitter + myFilePNG,
  768. },
  769. expectedValue: myContents,
  770. expectedErr: nil,
  771. },
  772. },
  773. },
  774. {
  775. setupNote: "files are loaded",
  776. provider: &ProviderOnePassword{
  777. vaults: map[string]int{myVault: 1},
  778. client: fake.NewMockClient().
  779. AddPredictableVault(myVault).
  780. AppendItem(myVaultID, onepassword.Item{
  781. ID: myItemID,
  782. Title: myItem,
  783. Vault: onepassword.ItemVault{ID: myVaultID},
  784. Category: documentCategory,
  785. Files: []*onepassword.File{
  786. {
  787. ID: myFilePNGID,
  788. Name: myFilePNG,
  789. },
  790. },
  791. }).
  792. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  793. Label: key1,
  794. Value: value2,
  795. }).
  796. SetFileContents(myFilePNG, []byte(myContents)),
  797. },
  798. checks: []check{
  799. {
  800. checkNote: "field named password",
  801. ref: esv1.ExternalSecretDataRemoteRef{
  802. Key: myItem,
  803. Property: fieldPrefix + prefixSplitter + key1,
  804. },
  805. expectedValue: value2,
  806. expectedErr: nil,
  807. },
  808. {
  809. checkNote: "file named my-file.png",
  810. ref: esv1.ExternalSecretDataRemoteRef{
  811. Key: myItem,
  812. Property: myFilePNG,
  813. },
  814. expectedValue: myContents,
  815. expectedErr: nil,
  816. },
  817. {
  818. checkNote: "file named my-file.png with prefix",
  819. ref: esv1.ExternalSecretDataRemoteRef{
  820. Key: myItem,
  821. Property: filePrefix + prefixSplitter + myFilePNG,
  822. },
  823. expectedValue: myContents,
  824. expectedErr: nil,
  825. },
  826. {
  827. checkNote: "empty ref.Property",
  828. ref: esv1.ExternalSecretDataRemoteRef{
  829. Key: myItem,
  830. },
  831. expectedValue: myContents,
  832. expectedErr: nil,
  833. },
  834. {
  835. checkNote: "file non existent",
  836. ref: esv1.ExternalSecretDataRemoteRef{
  837. Key: myItem,
  838. Property: "you-cant-find-me.png",
  839. },
  840. expectedErr: fmt.Errorf(errDocumentNotFound, errors.New("'my-item', 'you-cant-find-me.png'")),
  841. },
  842. {
  843. checkNote: "file non existent with prefix",
  844. ref: esv1.ExternalSecretDataRemoteRef{
  845. Key: myItem,
  846. Property: "file/you-cant-find-me.png",
  847. },
  848. expectedErr: fmt.Errorf(errDocumentNotFound, errors.New("'my-item', 'you-cant-find-me.png'")),
  849. },
  850. },
  851. },
  852. {
  853. setupNote: "one vault, one item, two fields w/ same Label",
  854. provider: &ProviderOnePassword{
  855. vaults: map[string]int{myVault: 1},
  856. client: fake.NewMockClient().
  857. AddPredictableVault(myVault).
  858. AddPredictableItemWithField(myVault, myItem, key1, value1).
  859. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  860. Label: key1,
  861. Value: value2,
  862. }),
  863. },
  864. checks: []check{
  865. {
  866. checkNote: key1,
  867. ref: esv1.ExternalSecretDataRemoteRef{
  868. Key: myItem,
  869. Property: key1,
  870. },
  871. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneField, "'key1' in 'my-item', got 2"),
  872. },
  873. },
  874. },
  875. }
  876. // run the tests
  877. for _, tc := range testCases {
  878. for _, check := range tc.checks {
  879. got, err := tc.provider.GetSecret(context.Background(), check.ref)
  880. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  881. if check.expectedErr == nil && err != nil {
  882. // expected no error, got one
  883. t.Errorf(getSecretErrFormat, notes, nil, err)
  884. }
  885. if check.expectedErr != nil && err == nil {
  886. // expected an error, didn't get one
  887. t.Errorf(getSecretErrFormat, notes, check.expectedErr.Error(), nil)
  888. }
  889. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  890. // expected an error, got the wrong one
  891. t.Errorf(getSecretErrFormat, notes, check.expectedErr.Error(), err.Error())
  892. }
  893. if check.expectedValue != "" {
  894. if check.expectedValue != string(got) {
  895. // expected a predefined value, got something else
  896. t.Errorf(getSecretErrFormat, notes, check.expectedValue, string(got))
  897. }
  898. }
  899. }
  900. }
  901. }
  902. // most functionality is tested in TestFindItem. here we just check:
  903. //
  904. // all keys are fetched and the map is compiled correctly,
  905. // files are loaded, and the data or errors are properly returned.
  906. func TestGetSecretMap(t *testing.T) {
  907. type check struct {
  908. checkNote string
  909. ref esv1.ExternalSecretDataRemoteRef
  910. expectedMap map[string][]byte
  911. expectedErr error
  912. }
  913. type testCase struct {
  914. setupNote string
  915. provider *ProviderOnePassword
  916. checks []check
  917. }
  918. testCases := []testCase{
  919. {
  920. setupNote: "one vault, one item, two fields",
  921. provider: &ProviderOnePassword{
  922. vaults: map[string]int{myVault: 1},
  923. client: fake.NewMockClient().
  924. AddPredictableVault(myVault).
  925. AppendItem(myVaultID, onepassword.Item{
  926. ID: myItemID,
  927. Title: myItem,
  928. Vault: onepassword.ItemVault{ID: myVaultID},
  929. Files: []*onepassword.File{
  930. {
  931. ID: myFilePNGID,
  932. Name: myFilePNG,
  933. },
  934. {
  935. ID: myFile2ID,
  936. Name: myFile2PNG,
  937. },
  938. },
  939. }).
  940. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  941. Label: key1,
  942. Value: value1,
  943. }).
  944. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  945. Label: password,
  946. Value: value2,
  947. }).
  948. SetFileContents(myFilePNG, []byte(myContents)).
  949. SetFileContents(myFile2PNG, []byte(myContents2)),
  950. },
  951. checks: []check{
  952. {
  953. checkNote: "all Properties",
  954. ref: esv1.ExternalSecretDataRemoteRef{
  955. Key: myItem,
  956. },
  957. expectedMap: map[string][]byte{
  958. key1: []byte(value1),
  959. password: []byte(value2),
  960. },
  961. expectedErr: nil,
  962. },
  963. {
  964. checkNote: "limit by Property",
  965. ref: esv1.ExternalSecretDataRemoteRef{
  966. Key: myItem,
  967. Property: password,
  968. },
  969. expectedMap: map[string][]byte{
  970. password: []byte(value2),
  971. },
  972. expectedErr: nil,
  973. },
  974. {
  975. checkNote: "'ref.version' not implemented",
  976. ref: esv1.ExternalSecretDataRemoteRef{
  977. Key: myItem,
  978. Property: key1,
  979. Version: "123",
  980. },
  981. expectedErr: errors.New(errVersionNotImplemented),
  982. },
  983. {
  984. checkNote: "limit by Property with prefix",
  985. ref: esv1.ExternalSecretDataRemoteRef{
  986. Key: myItem,
  987. Property: filePrefix + prefixSplitter + myFilePNG,
  988. },
  989. expectedMap: map[string][]byte{
  990. myFilePNG: []byte(myContents),
  991. },
  992. expectedErr: nil,
  993. },
  994. },
  995. },
  996. {
  997. setupNote: "files",
  998. provider: &ProviderOnePassword{
  999. vaults: map[string]int{myVault: 1},
  1000. client: fake.NewMockClient().
  1001. AddPredictableVault(myVault).
  1002. AppendItem(myVaultID, onepassword.Item{
  1003. ID: myItemID,
  1004. Title: myItem,
  1005. Vault: onepassword.ItemVault{ID: myVaultID},
  1006. Category: documentCategory,
  1007. Files: []*onepassword.File{
  1008. {
  1009. ID: myFilePNGID,
  1010. Name: myFilePNG,
  1011. },
  1012. {
  1013. ID: myFile2ID,
  1014. Name: myFile2PNG,
  1015. },
  1016. },
  1017. }).
  1018. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1019. Label: key1,
  1020. Value: value2,
  1021. }).
  1022. SetFileContents(myFilePNG, []byte(myContents)).
  1023. SetFileContents(myFile2PNG, []byte(myContents2)),
  1024. },
  1025. checks: []check{
  1026. {
  1027. checkNote: "all Properties",
  1028. ref: esv1.ExternalSecretDataRemoteRef{
  1029. Key: myItem,
  1030. },
  1031. expectedMap: map[string][]byte{
  1032. myFilePNG: []byte(myContents),
  1033. myFile2PNG: []byte(myContents2),
  1034. },
  1035. expectedErr: nil,
  1036. },
  1037. {
  1038. checkNote: "limit by Property",
  1039. ref: esv1.ExternalSecretDataRemoteRef{
  1040. Key: myItem,
  1041. Property: myFilePNG,
  1042. },
  1043. expectedMap: map[string][]byte{
  1044. myFilePNG: []byte(myContents),
  1045. },
  1046. expectedErr: nil,
  1047. },
  1048. {
  1049. checkNote: "limit by Property with prefix",
  1050. ref: esv1.ExternalSecretDataRemoteRef{
  1051. Key: myItem,
  1052. Property: filePrefix + prefixSplitter + myFilePNG,
  1053. },
  1054. expectedMap: map[string][]byte{
  1055. myFilePNG: []byte(myContents),
  1056. },
  1057. expectedErr: nil,
  1058. },
  1059. {
  1060. checkNote: "get field limit by Property",
  1061. ref: esv1.ExternalSecretDataRemoteRef{
  1062. Key: myItem,
  1063. Property: fieldPrefix + prefixSplitter + key1,
  1064. },
  1065. expectedMap: map[string][]byte{
  1066. key1: []byte(value2),
  1067. },
  1068. expectedErr: nil,
  1069. },
  1070. },
  1071. },
  1072. {
  1073. setupNote: "one vault, one item, two fields w/ same Label",
  1074. provider: &ProviderOnePassword{
  1075. vaults: map[string]int{myVault: 1},
  1076. client: fake.NewMockClient().
  1077. AddPredictableVault(myVault).
  1078. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1079. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1080. Label: key1,
  1081. Value: value2,
  1082. }),
  1083. },
  1084. checks: []check{
  1085. {
  1086. checkNote: key1,
  1087. ref: esv1.ExternalSecretDataRemoteRef{
  1088. Key: myItem,
  1089. },
  1090. expectedMap: nil,
  1091. expectedErr: fmt.Errorf(errFromErrMsgF, ErrExpectedOneField, "'key1' in 'my-item', got 2"),
  1092. },
  1093. },
  1094. },
  1095. }
  1096. // run the tests
  1097. for _, tc := range testCases {
  1098. for _, check := range tc.checks {
  1099. gotMap, err := tc.provider.GetSecretMap(context.Background(), check.ref)
  1100. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  1101. if check.expectedErr == nil && err != nil {
  1102. // expected no error, got one
  1103. t.Errorf(getSecretMapErrFormat, notes, nil, err)
  1104. }
  1105. if check.expectedErr != nil && err == nil {
  1106. // expected an error, didn't get one
  1107. t.Errorf(getSecretMapErrFormat, notes, check.expectedErr.Error(), nil)
  1108. }
  1109. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  1110. // expected an error, got the wrong one
  1111. t.Errorf(getSecretMapErrFormat, notes, check.expectedErr.Error(), err.Error())
  1112. }
  1113. if !reflect.DeepEqual(check.expectedMap, gotMap) {
  1114. // expected a predefined map, got something else
  1115. t.Errorf(getSecretMapErrFormat, notes, check.expectedMap, gotMap)
  1116. }
  1117. }
  1118. }
  1119. }
  1120. func TestGetAllSecrets(t *testing.T) {
  1121. type check struct {
  1122. checkNote string
  1123. ref esv1.ExternalSecretFind
  1124. expectedMap map[string][]byte
  1125. expectedErr error
  1126. }
  1127. type testCase struct {
  1128. setupNote string
  1129. provider *ProviderOnePassword
  1130. checks []check
  1131. }
  1132. testCases := []testCase{
  1133. {
  1134. setupNote: "three vaults, three items, all different field Labels",
  1135. provider: &ProviderOnePassword{
  1136. vaults: map[string]int{myVault: 1, myOtherVault: 2, myNonMatchingVault: 3},
  1137. client: fake.NewMockClient().
  1138. AddPredictableVault(myVault).
  1139. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1140. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1141. Label: key2,
  1142. Value: value2,
  1143. }).
  1144. AddPredictableVault(myOtherVault).
  1145. AddPredictableItemWithField(myOtherVault, myOtherItem, key3, value3).
  1146. AppendItemField(myOtherVaultID, myOtherItemID, onepassword.ItemField{
  1147. Label: key4,
  1148. Value: value4,
  1149. }).
  1150. AddPredictableVault(myNonMatchingVault).
  1151. AddPredictableItemWithField(myNonMatchingVault, myNonMatchingItem, "non-matching5", "value5").
  1152. AppendItemField(myNonMatchingVaultID, myNonMatchingItemID, onepassword.ItemField{
  1153. Label: "non-matching6",
  1154. Value: "value6",
  1155. }),
  1156. },
  1157. checks: []check{
  1158. {
  1159. checkNote: "find some with path only",
  1160. ref: esv1.ExternalSecretFind{
  1161. Path: new(myItem),
  1162. },
  1163. expectedMap: map[string][]byte{
  1164. key1: []byte(value1),
  1165. key2: []byte(value2),
  1166. },
  1167. expectedErr: nil,
  1168. },
  1169. {
  1170. checkNote: "find most with regex 'key*'",
  1171. ref: esv1.ExternalSecretFind{
  1172. Name: &esv1.FindName{
  1173. RegExp: "key*",
  1174. },
  1175. },
  1176. expectedMap: map[string][]byte{
  1177. key1: []byte(value1),
  1178. key2: []byte(value2),
  1179. key3: []byte(value3),
  1180. key4: []byte(value4),
  1181. },
  1182. expectedErr: nil,
  1183. },
  1184. {
  1185. checkNote: "find some with regex 'key*' and path 'my-other-item'",
  1186. ref: esv1.ExternalSecretFind{
  1187. Name: &esv1.FindName{
  1188. RegExp: "key*",
  1189. },
  1190. Path: new(myOtherItem),
  1191. },
  1192. expectedMap: map[string][]byte{
  1193. key3: []byte(value3),
  1194. key4: []byte(value4),
  1195. },
  1196. expectedErr: nil,
  1197. },
  1198. {
  1199. checkNote: "find none with regex 'asdf*'",
  1200. ref: esv1.ExternalSecretFind{
  1201. Name: &esv1.FindName{
  1202. RegExp: "asdf*",
  1203. },
  1204. },
  1205. expectedMap: map[string][]byte{},
  1206. expectedErr: nil,
  1207. },
  1208. {
  1209. checkNote: "find none with path 'no-exist'",
  1210. ref: esv1.ExternalSecretFind{
  1211. Name: &esv1.FindName{
  1212. RegExp: "key*",
  1213. },
  1214. Path: new("no-exist"),
  1215. },
  1216. expectedMap: map[string][]byte{},
  1217. expectedErr: nil,
  1218. },
  1219. },
  1220. },
  1221. {
  1222. setupNote: "one vault, three items, find by tags",
  1223. provider: &ProviderOnePassword{
  1224. vaults: map[string]int{myVault: 1},
  1225. client: fake.NewMockClient().
  1226. AddPredictableVault(myVault).
  1227. AppendItem(myVaultID, onepassword.Item{
  1228. ID: myItemID,
  1229. Title: myItem,
  1230. Tags: []string{"foo", "bar"},
  1231. Vault: onepassword.ItemVault{ID: myVaultID},
  1232. }).
  1233. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1234. Label: key1,
  1235. Value: value1,
  1236. }).
  1237. AppendItemField(myVaultID, myItemID, onepassword.ItemField{
  1238. Label: key2,
  1239. Value: value2,
  1240. }).
  1241. AppendItem(myVaultID, onepassword.Item{
  1242. ID: "my-item-id-2",
  1243. Title: "my-item-2",
  1244. Vault: onepassword.ItemVault{ID: myVaultID},
  1245. Tags: []string{"foo", "baz"},
  1246. }).
  1247. AppendItemField(myVaultID, "my-item-id-2", onepassword.ItemField{
  1248. Label: key3,
  1249. Value: value3,
  1250. }).
  1251. AppendItem(myVaultID, onepassword.Item{
  1252. ID: "my-item-id-3",
  1253. Title: "my-item-3",
  1254. Vault: onepassword.ItemVault{ID: myVaultID},
  1255. Tags: []string{"bang", "bing"},
  1256. }).
  1257. AppendItemField(myVaultID, "my-item-id-3", onepassword.ItemField{
  1258. Label: key4,
  1259. Value: value4,
  1260. }),
  1261. },
  1262. checks: []check{
  1263. {
  1264. checkNote: "find with tags",
  1265. ref: esv1.ExternalSecretFind{
  1266. Path: new(myItem),
  1267. Tags: map[string]string{
  1268. "foo": "true",
  1269. "bar": "true",
  1270. },
  1271. },
  1272. expectedMap: map[string][]byte{
  1273. key1: []byte(value1),
  1274. key2: []byte(value2),
  1275. },
  1276. expectedErr: nil,
  1277. },
  1278. {
  1279. checkNote: "find with tags and get all",
  1280. ref: esv1.ExternalSecretFind{
  1281. Path: new(myItem),
  1282. Tags: map[string]string{
  1283. "foo": "true",
  1284. },
  1285. },
  1286. expectedMap: map[string][]byte{
  1287. key1: []byte(value1),
  1288. key2: []byte(value2),
  1289. key3: []byte(value3),
  1290. },
  1291. expectedErr: nil,
  1292. },
  1293. },
  1294. },
  1295. {
  1296. setupNote: "3 vaults, 4 items, 5 files",
  1297. provider: &ProviderOnePassword{
  1298. vaults: map[string]int{myVault: 1, myOtherVault: 2, myNonMatchingVault: 3},
  1299. client: fake.NewMockClient().
  1300. // my-vault
  1301. AddPredictableVault(myVault).
  1302. AppendItem(myVaultID, onepassword.Item{
  1303. ID: myItemID,
  1304. Title: myItem,
  1305. Vault: onepassword.ItemVault{ID: myVaultID},
  1306. Category: documentCategory,
  1307. Files: []*onepassword.File{
  1308. {
  1309. ID: myFilePNGID,
  1310. Name: myFilePNG,
  1311. },
  1312. {
  1313. ID: mySecondFileTXTID,
  1314. Name: mySecondFileTXT,
  1315. },
  1316. },
  1317. }).
  1318. SetFileContents(myFilePNG, []byte(myContents)).
  1319. SetFileContents(mySecondFileTXT, []byte(mySecondContents)).
  1320. AppendItem(myVaultID, onepassword.Item{
  1321. ID: "my-item-2-id",
  1322. Title: "my-item-2",
  1323. Vault: onepassword.ItemVault{ID: myVaultID},
  1324. Category: documentCategory,
  1325. Files: []*onepassword.File{
  1326. {
  1327. ID: myFile2ID,
  1328. Name: myFile2TXT,
  1329. },
  1330. },
  1331. }).
  1332. SetFileContents(myFile2TXT, []byte(myContents2)).
  1333. // my-other-vault
  1334. AddPredictableVault(myOtherVault).
  1335. AppendItem(myOtherVaultID, onepassword.Item{
  1336. ID: myOtherItemID,
  1337. Title: myOtherItem,
  1338. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  1339. Category: documentCategory,
  1340. Files: []*onepassword.File{
  1341. {
  1342. ID: myOtherFilePNGID,
  1343. Name: myOtherFilePNG,
  1344. },
  1345. },
  1346. }).
  1347. SetFileContents(myOtherFilePNG, []byte(myOtherContents)).
  1348. // my-non-matching-vault
  1349. AddPredictableVault(myNonMatchingVault).
  1350. AppendItem(myNonMatchingVaultID, onepassword.Item{
  1351. ID: myNonMatchingItemID,
  1352. Title: myNonMatchingItem,
  1353. Vault: onepassword.ItemVault{ID: myNonMatchingVaultID},
  1354. Category: documentCategory,
  1355. Files: []*onepassword.File{
  1356. {
  1357. ID: nonMatchingFilePNGID,
  1358. Name: nonMatchingFilePNG,
  1359. },
  1360. },
  1361. }).
  1362. SetFileContents(nonMatchingFilePNG, []byte(nonMatchingContents)),
  1363. },
  1364. checks: []check{
  1365. {
  1366. checkNote: "find most with regex '^my-*'",
  1367. ref: esv1.ExternalSecretFind{
  1368. Name: &esv1.FindName{
  1369. RegExp: "^my-*",
  1370. },
  1371. },
  1372. expectedMap: map[string][]byte{
  1373. myFilePNG: []byte(myContents),
  1374. mySecondFileTXT: []byte(mySecondContents),
  1375. myFile2TXT: []byte(myContents2),
  1376. myOtherFilePNG: []byte(myOtherContents),
  1377. },
  1378. expectedErr: nil,
  1379. },
  1380. {
  1381. checkNote: "find some with regex '^my-*' and path 'my-other-item'",
  1382. ref: esv1.ExternalSecretFind{
  1383. Name: &esv1.FindName{
  1384. RegExp: "^my-*",
  1385. },
  1386. Path: new(myOtherItem),
  1387. },
  1388. expectedMap: map[string][]byte{
  1389. myOtherFilePNG: []byte(myOtherContents),
  1390. },
  1391. expectedErr: nil,
  1392. },
  1393. {
  1394. checkNote: "find none with regex '^asdf*'",
  1395. ref: esv1.ExternalSecretFind{
  1396. Name: &esv1.FindName{
  1397. RegExp: "^asdf*",
  1398. },
  1399. },
  1400. expectedMap: map[string][]byte{},
  1401. expectedErr: nil,
  1402. },
  1403. {
  1404. checkNote: "find none with path 'no-exist'",
  1405. ref: esv1.ExternalSecretFind{
  1406. Name: &esv1.FindName{
  1407. RegExp: "^my-*",
  1408. },
  1409. Path: new("no-exist"),
  1410. },
  1411. expectedMap: map[string][]byte{},
  1412. expectedErr: nil,
  1413. },
  1414. },
  1415. },
  1416. {
  1417. setupNote: "two fields/files with same name, first one wins",
  1418. provider: &ProviderOnePassword{
  1419. vaults: map[string]int{myVault: 1, myOtherVault: 2},
  1420. client: fake.NewMockClient().
  1421. // my-vault
  1422. AddPredictableVault(myVault).
  1423. AddPredictableItemWithField(myVault, myItem, key1, value1).
  1424. AddPredictableItemWithField(myVault, "my-second-item", key1, "value-second").
  1425. AppendItem(myVaultID, onepassword.Item{
  1426. ID: "file-item-id",
  1427. Title: "file-item",
  1428. Vault: onepassword.ItemVault{ID: myVaultID},
  1429. Category: documentCategory,
  1430. Files: []*onepassword.File{
  1431. {
  1432. ID: filePNGID,
  1433. Name: filePNG,
  1434. },
  1435. },
  1436. }).
  1437. SetFileContents(filePNG, []byte(myContents)).
  1438. AppendItem(myVaultID, onepassword.Item{
  1439. ID: "file-item-2-id",
  1440. Title: "file-item-2",
  1441. Vault: onepassword.ItemVault{ID: myVaultID},
  1442. Category: documentCategory,
  1443. Files: []*onepassword.File{
  1444. {
  1445. ID: "file-2-id",
  1446. Name: filePNG,
  1447. },
  1448. },
  1449. }).
  1450. // my-other-vault
  1451. AddPredictableVault(myOtherVault).
  1452. AddPredictableItemWithField(myOtherVault, myOtherItem, key1, "value-other").
  1453. AppendItem(myOtherVaultID, onepassword.Item{
  1454. ID: "file-item-other-id",
  1455. Title: "file-item-other",
  1456. Vault: onepassword.ItemVault{ID: myOtherVaultID},
  1457. Category: documentCategory,
  1458. Files: []*onepassword.File{
  1459. {
  1460. ID: "other-file-id",
  1461. Name: filePNG,
  1462. },
  1463. },
  1464. }),
  1465. },
  1466. checks: []check{
  1467. {
  1468. checkNote: "find fields with regex '^key*'",
  1469. ref: esv1.ExternalSecretFind{
  1470. Name: &esv1.FindName{
  1471. RegExp: "^key*",
  1472. },
  1473. },
  1474. expectedMap: map[string][]byte{
  1475. key1: []byte(value1),
  1476. },
  1477. expectedErr: nil,
  1478. },
  1479. {
  1480. checkNote: "find files with regex '^file*item*'",
  1481. ref: esv1.ExternalSecretFind{
  1482. Name: &esv1.FindName{
  1483. RegExp: "^file*",
  1484. },
  1485. },
  1486. expectedMap: map[string][]byte{
  1487. filePNG: []byte(myContents),
  1488. },
  1489. expectedErr: nil,
  1490. },
  1491. },
  1492. },
  1493. }
  1494. // run the tests
  1495. for _, tc := range testCases {
  1496. for _, check := range tc.checks {
  1497. gotMap, err := tc.provider.GetAllSecrets(context.Background(), check.ref)
  1498. notes := fmt.Sprintf(setupCheckFormat, tc.setupNote, check.checkNote)
  1499. if check.expectedErr == nil && err != nil {
  1500. // expected no error, got one
  1501. t.Fatalf(getAllSecretsErrFormat, notes, nil, err)
  1502. }
  1503. if check.expectedErr != nil && err == nil {
  1504. // expected an error, didn't get one
  1505. t.Errorf(getAllSecretsErrFormat, notes, check.expectedErr.Error(), nil)
  1506. }
  1507. if check.expectedErr != nil && err != nil && err.Error() != check.expectedErr.Error() {
  1508. // expected an error, got the wrong one
  1509. t.Errorf(getAllSecretsErrFormat, notes, check.expectedErr.Error(), err.Error())
  1510. }
  1511. if !reflect.DeepEqual(check.expectedMap, gotMap) {
  1512. // expected a predefined map, got something else
  1513. t.Errorf(getAllSecretsErrFormat, notes, check.expectedMap, gotMap)
  1514. }
  1515. }
  1516. }
  1517. }
  1518. func TestSortVaults(t *testing.T) {
  1519. type testCase struct {
  1520. vaults map[string]int
  1521. expected []string
  1522. }
  1523. testCases := []testCase{
  1524. {
  1525. vaults: map[string]int{
  1526. one: 1,
  1527. three: 3,
  1528. two: 2,
  1529. },
  1530. expected: []string{
  1531. one,
  1532. two,
  1533. three,
  1534. },
  1535. },
  1536. {
  1537. vaults: map[string]int{
  1538. "four": 100,
  1539. one: 1,
  1540. three: 3,
  1541. two: 2,
  1542. },
  1543. expected: []string{
  1544. one,
  1545. two,
  1546. three,
  1547. "four",
  1548. },
  1549. },
  1550. }
  1551. // run the tests
  1552. for _, tc := range testCases {
  1553. got := sortVaults(tc.vaults)
  1554. if !reflect.DeepEqual(got, tc.expected) {
  1555. t.Errorf("onepassword.sortVaults(...): -expected, +got:\n-%#v\n+%#v\n", tc.expected, got)
  1556. }
  1557. }
  1558. }
  1559. func TestIsNativeItemID(t *testing.T) {
  1560. tests := []struct {
  1561. name string
  1562. input string
  1563. expected bool
  1564. }{
  1565. {"valid native ID", "gdpvdudxrico74msloimk7qjna", true},
  1566. {"valid native ID all letters", "abcdefghijklmnopqrstuvwxyz", true},
  1567. {"valid native ID with digits", "abcdefghij0123456789abcdef", true},
  1568. {"too short", "gdpvdudxrico74msloimk7qjn", false},
  1569. {"too long", "gdpvdudxrico74msloimk7qjnaa", false},
  1570. {"empty string", "", false},
  1571. {"contains uppercase", "Gdpvdudxrico74msloimk7qjna", false},
  1572. {"contains special char", "gdpvdudxrico7-msloimk7qjna", false},
  1573. {"RFC 4122 UUID", "687adbe7-e6d2-4059-9a62-dbb95d291143", false},
  1574. {"item title", "My App (Production)", false},
  1575. }
  1576. for _, tt := range tests {
  1577. t.Run(tt.name, func(t *testing.T) {
  1578. got := isNativeItemID(tt.input)
  1579. if got != tt.expected {
  1580. t.Errorf("isNativeItemID(%q) = %v, want %v", tt.input, got, tt.expected)
  1581. }
  1582. })
  1583. }
  1584. }
  1585. func TestHasUniqueVaultNumbers(t *testing.T) {
  1586. type testCase struct {
  1587. vaults map[string]int
  1588. expected bool
  1589. }
  1590. testCases := []testCase{
  1591. {
  1592. vaults: map[string]int{
  1593. one: 1,
  1594. three: 3,
  1595. two: 2,
  1596. },
  1597. expected: true,
  1598. },
  1599. {
  1600. vaults: map[string]int{
  1601. "four": 100,
  1602. one: 1,
  1603. three: 3,
  1604. two: 2,
  1605. "eight": 100,
  1606. },
  1607. expected: false,
  1608. },
  1609. {
  1610. vaults: map[string]int{
  1611. one: 1,
  1612. "1": 1,
  1613. three: 3,
  1614. two: 2,
  1615. },
  1616. expected: false,
  1617. },
  1618. }
  1619. // run the tests
  1620. for _, tc := range testCases {
  1621. got := hasUniqueVaultNumbers(tc.vaults)
  1622. if got != tc.expected {
  1623. t.Errorf("onepassword.hasUniqueVaultNumbers(...): -expected, +got:\n-%#v\n+%#v\n", tc.expected, got)
  1624. }
  1625. }
  1626. }
  1627. type fakeRef struct {
  1628. key string
  1629. prop string
  1630. secretKey string
  1631. metadata *apiextensionsv1.JSON
  1632. }
  1633. func (f fakeRef) GetRemoteKey() string {
  1634. return f.key
  1635. }
  1636. func (f fakeRef) GetProperty() string {
  1637. return f.prop
  1638. }
  1639. func (f fakeRef) GetSecretKey() string {
  1640. return f.secretKey
  1641. }
  1642. func (f fakeRef) GetMetadata() *apiextensionsv1.JSON {
  1643. return f.metadata
  1644. }
  1645. func validateItem(t *testing.T, expectedItem, actualItem *onepassword.Item) {
  1646. t.Helper()
  1647. if !reflect.DeepEqual(expectedItem, actualItem) {
  1648. t.Errorf("expected item %v, got %v", expectedItem, actualItem)
  1649. }
  1650. }
  1651. func TestProviderOnePasswordCreateItem(t *testing.T) {
  1652. type testCase struct {
  1653. vaults map[string]int
  1654. expectedErr error
  1655. setupNote string
  1656. val []byte
  1657. createValidateFunc func(*testing.T, *onepassword.Item, string) (*onepassword.Item, error)
  1658. ref esv1.PushSecretData
  1659. }
  1660. const vaultName = "vault1"
  1661. const fallbackVaultName = "vault2"
  1662. thridPartyErr := errors.New("third party error")
  1663. metadata := &metadata.PushSecretMetadata[PushSecretMetadataSpec]{
  1664. APIVersion: metadata.APIVersion,
  1665. Kind: metadata.Kind,
  1666. Spec: PushSecretMetadataSpec{
  1667. Tags: []string{"tag1", "tag2"},
  1668. Vault: fallbackVaultName,
  1669. },
  1670. }
  1671. metadataRaw, _ := json.Marshal(metadata)
  1672. testCases := []testCase{
  1673. {
  1674. setupNote: "standard create",
  1675. val: []byte("value"),
  1676. ref: fakeRef{
  1677. key: "testing",
  1678. prop: "prop",
  1679. },
  1680. expectedErr: nil,
  1681. vaults: map[string]int{
  1682. vaultName: 1,
  1683. fallbackVaultName: 2,
  1684. },
  1685. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1686. validateItem(t, &onepassword.Item{
  1687. Title: "testing",
  1688. Category: onepassword.Server,
  1689. Vault: onepassword.ItemVault{
  1690. ID: vaultName,
  1691. },
  1692. Fields: []*onepassword.ItemField{
  1693. generateNewItemField("prop", "value"),
  1694. },
  1695. }, item)
  1696. return item, nil
  1697. },
  1698. },
  1699. {
  1700. setupNote: "standard create with no property",
  1701. val: []byte("value2"),
  1702. ref: fakeRef{
  1703. key: "testing2",
  1704. prop: "",
  1705. },
  1706. vaults: map[string]int{
  1707. vaultName: 2,
  1708. },
  1709. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1710. validateItem(t, &onepassword.Item{
  1711. Title: "testing2",
  1712. Category: onepassword.Server,
  1713. Vault: onepassword.ItemVault{
  1714. ID: vaultName,
  1715. },
  1716. Fields: []*onepassword.ItemField{
  1717. generateNewItemField("password", "value2"),
  1718. },
  1719. }, item)
  1720. return item, nil
  1721. },
  1722. },
  1723. {
  1724. setupNote: "no vaults",
  1725. val: []byte("value"),
  1726. ref: fakeRef{
  1727. key: "testing",
  1728. prop: "prop",
  1729. },
  1730. vaults: map[string]int{},
  1731. expectedErr: ErrNoVaults,
  1732. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1733. t.Errorf("onepassword.createItem(...): should not have been called")
  1734. return nil, nil
  1735. },
  1736. },
  1737. {
  1738. setupNote: "error on create",
  1739. val: []byte("testing"),
  1740. ref: fakeRef{
  1741. key: "another",
  1742. prop: "property",
  1743. },
  1744. vaults: map[string]int{
  1745. vaultName: 1,
  1746. },
  1747. expectedErr: thridPartyErr,
  1748. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1749. validateItem(t, &onepassword.Item{
  1750. Title: "another",
  1751. Category: onepassword.Server,
  1752. Vault: onepassword.ItemVault{
  1753. ID: vaultName,
  1754. },
  1755. Fields: []*onepassword.ItemField{
  1756. generateNewItemField("property", "testing"),
  1757. },
  1758. }, item)
  1759. return nil, thridPartyErr
  1760. },
  1761. },
  1762. {
  1763. setupNote: "valid metadata overrides",
  1764. val: []byte("testing"),
  1765. ref: fakeRef{
  1766. key: "another",
  1767. prop: "property",
  1768. metadata: &apiextensionsv1.JSON{
  1769. Raw: metadataRaw,
  1770. },
  1771. },
  1772. vaults: map[string]int{
  1773. vaultName: 1,
  1774. fallbackVaultName: 2,
  1775. },
  1776. expectedErr: nil,
  1777. createValidateFunc: func(t *testing.T, item *onepassword.Item, s string) (*onepassword.Item, error) {
  1778. validateItem(t, &onepassword.Item{
  1779. Title: "another",
  1780. Category: onepassword.Server,
  1781. Vault: onepassword.ItemVault{
  1782. ID: fallbackVaultName,
  1783. },
  1784. Fields: []*onepassword.ItemField{
  1785. generateNewItemField("property", "testing"),
  1786. },
  1787. Tags: []string{"tag1", "tag2"},
  1788. }, item)
  1789. return item, nil
  1790. },
  1791. },
  1792. }
  1793. provider := &ProviderOnePassword{}
  1794. for _, tc := range testCases {
  1795. // setup
  1796. mockClient := fake.NewMockClient()
  1797. mockClient.CreateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  1798. i, e := tc.createValidateFunc(t, item, s)
  1799. return i, e
  1800. }
  1801. provider.client = mockClient
  1802. provider.vaults = tc.vaults
  1803. err := provider.createItem(tc.val, tc.ref)
  1804. if !errors.Is(err, tc.expectedErr) {
  1805. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  1806. }
  1807. }
  1808. }
  1809. func TestProviderOnePasswordDeleteItem(t *testing.T) {
  1810. type testCase struct {
  1811. inputFields []*onepassword.ItemField
  1812. fieldName string
  1813. expectedErr error
  1814. expectedFields []*onepassword.ItemField
  1815. setupNote string
  1816. }
  1817. field1, field2, field3, field4 := "field1", "field2", "field3", "field4"
  1818. testCases := []testCase{
  1819. {
  1820. setupNote: "one field to remove",
  1821. inputFields: []*onepassword.ItemField{
  1822. {
  1823. ID: field1,
  1824. Label: field1,
  1825. Type: onepassword.FieldTypeAddress,
  1826. },
  1827. {
  1828. ID: field2,
  1829. Label: field2,
  1830. Type: onepassword.FieldTypeString,
  1831. },
  1832. {
  1833. ID: field3,
  1834. Label: field3,
  1835. Type: onepassword.FieldTypeConcealed,
  1836. },
  1837. },
  1838. fieldName: field2,
  1839. expectedFields: []*onepassword.ItemField{
  1840. {
  1841. ID: field1,
  1842. Label: field1,
  1843. Type: onepassword.FieldTypeAddress,
  1844. },
  1845. {
  1846. ID: field3,
  1847. Label: field3,
  1848. Type: onepassword.FieldTypeConcealed,
  1849. },
  1850. },
  1851. },
  1852. {
  1853. setupNote: "no fields to remove",
  1854. inputFields: []*onepassword.ItemField{
  1855. {
  1856. ID: field1,
  1857. Label: field1,
  1858. Type: onepassword.FieldTypeAddress,
  1859. },
  1860. {
  1861. ID: field2,
  1862. Label: field2,
  1863. Type: onepassword.FieldTypeString,
  1864. },
  1865. {
  1866. ID: field3,
  1867. Label: field3,
  1868. Type: onepassword.FieldTypeConcealed,
  1869. },
  1870. },
  1871. expectedErr: nil,
  1872. fieldName: field4,
  1873. expectedFields: []*onepassword.ItemField{
  1874. {
  1875. ID: field1,
  1876. Label: field1,
  1877. Type: onepassword.FieldTypeAddress,
  1878. },
  1879. {
  1880. ID: field2,
  1881. Label: field2,
  1882. Type: onepassword.FieldTypeString,
  1883. },
  1884. {
  1885. ID: field3,
  1886. Label: field3,
  1887. Type: onepassword.FieldTypeConcealed,
  1888. },
  1889. },
  1890. },
  1891. {
  1892. setupNote: "multiple fields to remove",
  1893. inputFields: []*onepassword.ItemField{
  1894. {
  1895. ID: field3,
  1896. Label: field3,
  1897. Type: onepassword.FieldTypeConcealed,
  1898. },
  1899. {
  1900. ID: field1,
  1901. Label: field1,
  1902. Type: onepassword.FieldTypeAddress,
  1903. },
  1904. {
  1905. ID: field3,
  1906. Label: field3,
  1907. Type: onepassword.FieldTypeCreditCardType,
  1908. },
  1909. {
  1910. ID: field2,
  1911. Label: field2,
  1912. Type: onepassword.FieldTypeString,
  1913. },
  1914. {
  1915. ID: field3,
  1916. Label: field3,
  1917. Type: onepassword.FieldTypeGender,
  1918. },
  1919. },
  1920. fieldName: field3,
  1921. expectedErr: ErrExpectedOneField,
  1922. expectedFields: nil,
  1923. },
  1924. }
  1925. // run the tests
  1926. for _, tc := range testCases {
  1927. actualOutput, err := deleteField(tc.inputFields, tc.fieldName)
  1928. if len(actualOutput) != len(tc.expectedFields) {
  1929. t.Errorf("%s: length fields did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, tc.expectedFields, actualOutput)
  1930. return
  1931. }
  1932. if !errors.Is(err, tc.expectedErr) {
  1933. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  1934. }
  1935. for i, check := range tc.expectedFields {
  1936. if len(actualOutput) <= i {
  1937. continue
  1938. }
  1939. if !reflect.DeepEqual(check, actualOutput[i]) {
  1940. t.Errorf("%s: fields at position %d did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, i, check, actualOutput[i])
  1941. }
  1942. }
  1943. }
  1944. }
  1945. func TestUpdateFields(t *testing.T) {
  1946. type testCase struct {
  1947. inputFields []*onepassword.ItemField
  1948. fieldName string
  1949. newVal string
  1950. expectedErr error
  1951. expectedFields []*onepassword.ItemField
  1952. setupNote string
  1953. }
  1954. field1, field2, field3, field4 := "field1", "field2", "field3", "field4"
  1955. testCases := []testCase{
  1956. {
  1957. setupNote: "one field to update",
  1958. inputFields: []*onepassword.ItemField{
  1959. {
  1960. ID: field1,
  1961. Label: field1,
  1962. Value: value1,
  1963. Type: onepassword.FieldTypeAddress,
  1964. },
  1965. {
  1966. ID: field2,
  1967. Label: field2,
  1968. Value: value2,
  1969. Type: onepassword.FieldTypeString,
  1970. },
  1971. {
  1972. ID: field3,
  1973. Label: field3,
  1974. Value: value3,
  1975. Type: onepassword.FieldTypeConcealed,
  1976. },
  1977. },
  1978. fieldName: field2,
  1979. newVal: "testing",
  1980. expectedFields: []*onepassword.ItemField{
  1981. {
  1982. ID: field1,
  1983. Label: field1,
  1984. Value: value1,
  1985. Type: onepassword.FieldTypeAddress,
  1986. },
  1987. {
  1988. ID: field2,
  1989. Label: field2,
  1990. Value: "testing",
  1991. Type: onepassword.FieldTypeString,
  1992. },
  1993. {
  1994. ID: field3,
  1995. Label: field3,
  1996. Value: value3,
  1997. Type: onepassword.FieldTypeConcealed,
  1998. },
  1999. },
  2000. },
  2001. {
  2002. setupNote: "add field",
  2003. inputFields: []*onepassword.ItemField{
  2004. {
  2005. ID: field1,
  2006. Value: value1,
  2007. Label: field1,
  2008. Type: onepassword.FieldTypeAddress,
  2009. },
  2010. {
  2011. ID: field2,
  2012. Label: field2,
  2013. Value: value2,
  2014. Type: onepassword.FieldTypeString,
  2015. },
  2016. },
  2017. fieldName: field4,
  2018. newVal: value4,
  2019. expectedFields: []*onepassword.ItemField{
  2020. {
  2021. ID: field1,
  2022. Label: field1,
  2023. Value: value1,
  2024. Type: onepassword.FieldTypeAddress,
  2025. },
  2026. {
  2027. ID: field2,
  2028. Label: field2,
  2029. Value: value2,
  2030. Type: onepassword.FieldTypeString,
  2031. },
  2032. {
  2033. Label: field4,
  2034. Value: value4,
  2035. Type: onepassword.FieldTypeConcealed,
  2036. },
  2037. },
  2038. },
  2039. {
  2040. setupNote: "no changes",
  2041. inputFields: []*onepassword.ItemField{
  2042. {
  2043. ID: field1,
  2044. Label: field1,
  2045. Value: value1,
  2046. Type: onepassword.FieldTypeAddress,
  2047. },
  2048. {
  2049. ID: field2,
  2050. Label: field2,
  2051. Value: value2,
  2052. Type: onepassword.FieldTypeString,
  2053. },
  2054. },
  2055. fieldName: field1,
  2056. newVal: value1,
  2057. expectedErr: nil,
  2058. expectedFields: []*onepassword.ItemField{
  2059. {
  2060. ID: field1,
  2061. Label: field1,
  2062. Value: value1,
  2063. Type: onepassword.FieldTypeAddress,
  2064. },
  2065. {
  2066. ID: field2,
  2067. Label: field2,
  2068. Value: value2,
  2069. Type: onepassword.FieldTypeString,
  2070. },
  2071. },
  2072. },
  2073. {
  2074. setupNote: "multiple fields to remove",
  2075. inputFields: []*onepassword.ItemField{
  2076. {
  2077. ID: field3,
  2078. Label: field3,
  2079. Value: value3,
  2080. Type: onepassword.FieldTypeConcealed,
  2081. },
  2082. {
  2083. ID: field1,
  2084. Label: field1,
  2085. Value: value1,
  2086. Type: onepassword.FieldTypeAddress,
  2087. },
  2088. {
  2089. ID: field3,
  2090. Label: field3,
  2091. Value: value3,
  2092. Type: onepassword.FieldTypeCreditCardType,
  2093. },
  2094. {
  2095. ID: field2,
  2096. Label: field2,
  2097. Value: value2,
  2098. Type: onepassword.FieldTypeString,
  2099. },
  2100. {
  2101. ID: field3,
  2102. Label: field3,
  2103. Value: value3,
  2104. Type: onepassword.FieldTypeGender,
  2105. },
  2106. },
  2107. fieldName: field3,
  2108. expectedErr: ErrExpectedOneField,
  2109. expectedFields: nil,
  2110. },
  2111. }
  2112. // run the tests
  2113. for _, tc := range testCases {
  2114. actualOutput, err := updateFieldValue(tc.inputFields, tc.fieldName, tc.newVal)
  2115. if len(actualOutput) != len(tc.expectedFields) {
  2116. t.Errorf("%s: length fields did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, tc.expectedFields, actualOutput)
  2117. return
  2118. }
  2119. if !errors.Is(err, tc.expectedErr) {
  2120. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  2121. }
  2122. for i, check := range tc.expectedFields {
  2123. if len(actualOutput) <= i {
  2124. continue
  2125. }
  2126. if !reflect.DeepEqual(check, actualOutput[i]) {
  2127. t.Errorf("%s: fields at position %d did not match: -expected, +got:\n-%#v\n+%#v\n", tc.setupNote, i, check, actualOutput[i])
  2128. }
  2129. }
  2130. }
  2131. }
  2132. func TestGenerateNewItemField(t *testing.T) {
  2133. field := generateNewItemField("property", "testing")
  2134. if !reflect.DeepEqual(field, &onepassword.ItemField{
  2135. Label: "property",
  2136. Type: onepassword.FieldTypeConcealed,
  2137. Value: "testing",
  2138. }) {
  2139. t.Errorf("field did not match: -expected, +got:\n-%#v\n+%#v\n", &onepassword.ItemField{
  2140. Label: "property",
  2141. Type: onepassword.FieldTypeConcealed,
  2142. Value: "testing",
  2143. }, field)
  2144. }
  2145. }
  2146. func TestProviderOnePasswordPushSecret(t *testing.T) {
  2147. // Most logic is tested in the createItem and updateField functions
  2148. // This test is just to make sure the correct functions are called.
  2149. // the correct values are passed to them, and errors are propagated
  2150. type testCase struct {
  2151. vaults map[string]int
  2152. expectedErr error
  2153. setupNote string
  2154. existingItems []onepassword.Item
  2155. val *corev1.Secret
  2156. existingItemsFields map[string][]*onepassword.ItemField
  2157. createValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
  2158. updateValidateFunc func(*onepassword.Item, string) (*onepassword.Item, error)
  2159. ref fakeRef
  2160. }
  2161. var (
  2162. vaultName = "vault1"
  2163. vault = onepassword.Vault{
  2164. ID: vaultName,
  2165. }
  2166. )
  2167. metadata := &metadata.PushSecretMetadata[PushSecretMetadataSpec]{
  2168. APIVersion: metadata.APIVersion,
  2169. Kind: metadata.Kind,
  2170. Spec: PushSecretMetadataSpec{
  2171. Tags: []string{"tag1", "tag2"},
  2172. },
  2173. }
  2174. metadataRaw, _ := json.Marshal(metadata)
  2175. testCases := []testCase{
  2176. {
  2177. vaults: map[string]int{
  2178. vaultName: 1,
  2179. },
  2180. expectedErr: ErrExpectedOneItem,
  2181. setupNote: "find item error",
  2182. existingItems: []onepassword.Item{
  2183. {
  2184. Title: key1,
  2185. }, {
  2186. Title: key1,
  2187. }, // can be empty, testing for error with length
  2188. },
  2189. ref: fakeRef{
  2190. key: key1,
  2191. secretKey: key1,
  2192. },
  2193. val: &corev1.Secret{Data: map[string][]byte{key1: []byte("testing")}},
  2194. },
  2195. {
  2196. setupNote: "create item error",
  2197. expectedErr: ErrNoVaults,
  2198. val: &corev1.Secret{Data: map[string][]byte{key1: []byte("testing")}},
  2199. ref: fakeRef{secretKey: key1},
  2200. vaults: nil,
  2201. },
  2202. {
  2203. setupNote: "key not in data",
  2204. expectedErr: ErrKeyNotFound,
  2205. val: &corev1.Secret{Data: map[string][]byte{}},
  2206. ref: fakeRef{secretKey: key1},
  2207. vaults: nil,
  2208. },
  2209. {
  2210. setupNote: "create item success",
  2211. expectedErr: nil,
  2212. val: &corev1.Secret{Data: map[string][]byte{
  2213. key1: []byte("testing"),
  2214. }},
  2215. ref: fakeRef{
  2216. key: key1,
  2217. prop: "prop",
  2218. secretKey: key1,
  2219. },
  2220. vaults: map[string]int{
  2221. vaultName: 1,
  2222. },
  2223. createValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2224. validateItem(t, &onepassword.Item{
  2225. Title: key1,
  2226. Category: onepassword.Server,
  2227. Vault: onepassword.ItemVault{
  2228. ID: vaultName,
  2229. },
  2230. Fields: []*onepassword.ItemField{
  2231. generateNewItemField("prop", "testing"),
  2232. },
  2233. }, item)
  2234. return item, nil
  2235. },
  2236. },
  2237. {
  2238. setupNote: "update fields error",
  2239. expectedErr: ErrExpectedOneField,
  2240. val: &corev1.Secret{Data: map[string][]byte{
  2241. "key2": []byte("testing"),
  2242. }},
  2243. ref: fakeRef{
  2244. key: key1,
  2245. prop: "prop",
  2246. secretKey: "key2",
  2247. },
  2248. vaults: map[string]int{
  2249. vaultName: 1,
  2250. },
  2251. existingItemsFields: map[string][]*onepassword.ItemField{
  2252. key1: {
  2253. {
  2254. Label: "prop",
  2255. },
  2256. {
  2257. Label: "prop",
  2258. },
  2259. },
  2260. },
  2261. existingItems: []onepassword.Item{
  2262. {
  2263. Vault: onepassword.ItemVault{
  2264. ID: vaultName,
  2265. },
  2266. ID: key1,
  2267. Title: key1,
  2268. },
  2269. },
  2270. },
  2271. {
  2272. setupNote: "standard update",
  2273. expectedErr: nil,
  2274. val: &corev1.Secret{Data: map[string][]byte{
  2275. "key3": []byte("testing2"),
  2276. }},
  2277. ref: fakeRef{
  2278. key: key1,
  2279. prop: "",
  2280. secretKey: "key3",
  2281. },
  2282. vaults: map[string]int{
  2283. vaultName: 1,
  2284. },
  2285. existingItemsFields: map[string][]*onepassword.ItemField{
  2286. key1: {
  2287. {
  2288. Label: "not-prop",
  2289. },
  2290. },
  2291. },
  2292. updateValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2293. expectedItem := &onepassword.Item{
  2294. Vault: onepassword.ItemVault{
  2295. ID: vaultName,
  2296. },
  2297. ID: key1,
  2298. Title: key1,
  2299. Fields: []*onepassword.ItemField{
  2300. {
  2301. Label: "not-prop",
  2302. },
  2303. {
  2304. Label: "password",
  2305. Value: "testing2",
  2306. Type: onepassword.FieldTypeConcealed,
  2307. },
  2308. },
  2309. }
  2310. validateItem(t, expectedItem, item)
  2311. return expectedItem, nil
  2312. },
  2313. existingItems: []onepassword.Item{
  2314. {
  2315. Vault: onepassword.ItemVault{
  2316. ID: vaultName,
  2317. },
  2318. ID: key1,
  2319. Title: key1,
  2320. },
  2321. },
  2322. },
  2323. {
  2324. setupNote: "create item with metadata overwrites success",
  2325. expectedErr: nil,
  2326. val: &corev1.Secret{Data: map[string][]byte{
  2327. key1: []byte("testing"),
  2328. }},
  2329. ref: fakeRef{
  2330. key: key1,
  2331. prop: "prop",
  2332. secretKey: key1,
  2333. metadata: &apiextensionsv1.JSON{
  2334. Raw: metadataRaw,
  2335. },
  2336. },
  2337. vaults: map[string]int{
  2338. vaultName: 1,
  2339. },
  2340. createValidateFunc: func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2341. validateItem(t, &onepassword.Item{
  2342. Title: key1,
  2343. Category: onepassword.Server,
  2344. Vault: onepassword.ItemVault{
  2345. ID: vaultName,
  2346. },
  2347. Fields: []*onepassword.ItemField{
  2348. generateNewItemField("prop", "testing"),
  2349. },
  2350. Tags: []string{"tag1", "tag2"},
  2351. }, item)
  2352. return item, nil
  2353. },
  2354. },
  2355. }
  2356. provider := &ProviderOnePassword{}
  2357. for _, tc := range testCases {
  2358. t.Run(tc.setupNote, func(t *testing.T) {
  2359. // setup
  2360. mockClient := fake.NewMockClient()
  2361. mockClient.MockVaults = map[string][]onepassword.Vault{
  2362. vaultName: {vault},
  2363. }
  2364. mockClient.MockItems = map[string][]onepassword.Item{
  2365. vaultName: tc.existingItems,
  2366. }
  2367. mockClient.MockItemFields = map[string]map[string][]*onepassword.ItemField{
  2368. vaultName: tc.existingItemsFields,
  2369. }
  2370. mockClient.CreateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2371. return tc.createValidateFunc(item, s)
  2372. }
  2373. mockClient.UpdateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2374. return tc.updateValidateFunc(item, s)
  2375. }
  2376. provider.client = mockClient
  2377. provider.vaults = tc.vaults
  2378. err := provider.PushSecret(context.Background(), tc.val, tc.ref)
  2379. if !errors.Is(err, tc.expectedErr) {
  2380. t.Errorf(errDoesNotMatchMsgF, tc.setupNote, tc.expectedErr, err)
  2381. }
  2382. })
  2383. }
  2384. }
  2385. // mockClient implements connect.Client interface for testing.
  2386. type mockClient struct {
  2387. getItemsFunc func(vaultQuery string) ([]onepassword.Item, error)
  2388. }
  2389. func (m *mockClient) GetVaults() ([]onepassword.Vault, error) { return nil, nil }
  2390. func (m *mockClient) GetVault(uuid string) (*onepassword.Vault, error) { return nil, nil }
  2391. func (m *mockClient) GetVaultByUUID(uuid string) (*onepassword.Vault, error) { return nil, nil }
  2392. func (m *mockClient) GetVaultByTitle(title string) (*onepassword.Vault, error) { return nil, nil }
  2393. func (m *mockClient) GetVaultsByTitle(uuid string) ([]onepassword.Vault, error) { return nil, nil }
  2394. func (m *mockClient) GetItems(vaultQuery string) ([]onepassword.Item, error) {
  2395. if m.getItemsFunc != nil {
  2396. return m.getItemsFunc(vaultQuery)
  2397. }
  2398. return nil, nil
  2399. }
  2400. func (m *mockClient) GetItem(itemQuery, vaultQuery string) (*onepassword.Item, error) {
  2401. return nil, nil
  2402. }
  2403. func (m *mockClient) GetItemByUUID(uuid, vaultQuery string) (*onepassword.Item, error) {
  2404. return nil, nil
  2405. }
  2406. func (m *mockClient) GetItemByTitle(title, vaultQuery string) (*onepassword.Item, error) {
  2407. return nil, nil
  2408. }
  2409. func (m *mockClient) GetItemsByTitle(title, vaultQuery string) ([]onepassword.Item, error) {
  2410. return nil, nil
  2411. }
  2412. func (m *mockClient) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
  2413. return nil, nil
  2414. }
  2415. func (m *mockClient) UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
  2416. return nil, nil
  2417. }
  2418. func (m *mockClient) DeleteItem(item *onepassword.Item, vaultQuery string) error { return nil }
  2419. func (m *mockClient) DeleteItemByID(itemUUID, vaultQuery string) error { return nil }
  2420. func (m *mockClient) DeleteItemByTitle(title, vaultQuery string) error { return nil }
  2421. func (m *mockClient) GetFiles(itemQuery, vaultQuery string) ([]onepassword.File, error) {
  2422. return nil, nil
  2423. }
  2424. func (m *mockClient) GetFile(uuid, itemQuery, vaultQuery string) (*onepassword.File, error) {
  2425. return nil, nil
  2426. }
  2427. func (m *mockClient) GetFileContent(file *onepassword.File) ([]byte, error) { return nil, nil }
  2428. func (m *mockClient) DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error) {
  2429. return "", nil
  2430. }
  2431. func (m *mockClient) LoadStructFromItemByUUID(config any, itemUUID, vaultQuery string) error {
  2432. return nil
  2433. }
  2434. func (m *mockClient) LoadStructFromItemByTitle(config any, itemTitle, vaultQuery string) error {
  2435. return nil
  2436. }
  2437. func (m *mockClient) LoadStructFromItem(config any, itemQuery, vaultQuery string) error {
  2438. return nil
  2439. }
  2440. func (m *mockClient) LoadStruct(config any) error { return nil }
  2441. func TestDeleteSecretWithEmptySections(t *testing.T) {
  2442. const vaultName = "vault1"
  2443. vault := onepassword.Vault{
  2444. ID: vaultName,
  2445. Name: vaultName,
  2446. }
  2447. t.Run("item with empty section should be deleted when last field is removed", func(t *testing.T) {
  2448. deleteCalled := false
  2449. updateCalled := false
  2450. mockClient := fake.NewMockClient()
  2451. mockClient.MockVaults = map[string][]onepassword.Vault{
  2452. vaultName: {vault},
  2453. }
  2454. mockClient.MockItems = map[string][]onepassword.Item{
  2455. vaultName: {
  2456. {
  2457. ID: "item-id",
  2458. Title: "test-item",
  2459. Vault: onepassword.ItemVault{ID: vaultName},
  2460. Sections: []*onepassword.ItemSection{
  2461. {ID: "", Label: ""},
  2462. },
  2463. },
  2464. },
  2465. }
  2466. mockClient.MockItemFields = map[string]map[string][]*onepassword.ItemField{
  2467. vaultName: {
  2468. "item-id": {
  2469. {ID: "field-1", Label: "password", Value: "secret"},
  2470. },
  2471. },
  2472. }
  2473. mockClient.DeleteItemValidateFunc = func(item *onepassword.Item, s string) error {
  2474. deleteCalled = true
  2475. return nil
  2476. }
  2477. mockClient.UpdateItemValidateFunc = func(item *onepassword.Item, s string) (*onepassword.Item, error) {
  2478. updateCalled = true
  2479. return item, nil
  2480. }
  2481. provider := &ProviderOnePassword{
  2482. vaults: map[string]int{vaultName: 1},
  2483. client: mockClient,
  2484. }
  2485. err := provider.DeleteSecret(context.Background(), fakeRef{
  2486. key: "test-item",
  2487. prop: "password",
  2488. })
  2489. if err != nil {
  2490. t.Errorf("expected no error, got %v", err)
  2491. }
  2492. if !deleteCalled {
  2493. t.Error("expected DeleteItem to be called when item has no fields and only empty sections")
  2494. }
  2495. if updateCalled {
  2496. t.Error("expected UpdateItem not to be called")
  2497. }
  2498. })
  2499. t.Run("item not found should not error", func(t *testing.T) {
  2500. mockClient := fake.NewMockClient()
  2501. mockClient.MockVaults = map[string][]onepassword.Vault{
  2502. vaultName: {vault},
  2503. }
  2504. mockClient.MockItems = map[string][]onepassword.Item{
  2505. vaultName: {},
  2506. }
  2507. provider := &ProviderOnePassword{
  2508. vaults: map[string]int{vaultName: 1},
  2509. client: mockClient,
  2510. }
  2511. err := provider.DeleteSecret(context.Background(), fakeRef{
  2512. key: "non-existent-item",
  2513. prop: "password",
  2514. })
  2515. if err != nil {
  2516. t.Errorf("expected no error when item not found, got %v", err)
  2517. }
  2518. })
  2519. }
  2520. func TestRetryClient(t *testing.T) {
  2521. tests := []struct {
  2522. name string
  2523. err error
  2524. shouldRetry bool
  2525. expectErr bool
  2526. }{
  2527. {
  2528. name: "403 auth error should retry",
  2529. err: errors.New("status 403: Authorization failed"),
  2530. shouldRetry: true,
  2531. expectErr: true,
  2532. },
  2533. {
  2534. name: "other error should not retry",
  2535. err: errors.New("status 500: Internal Server Error"),
  2536. shouldRetry: false,
  2537. expectErr: true,
  2538. },
  2539. {
  2540. name: "nil error should not retry",
  2541. err: nil,
  2542. shouldRetry: false,
  2543. expectErr: false,
  2544. },
  2545. }
  2546. for _, tc := range tests {
  2547. t.Run(tc.name, func(t *testing.T) {
  2548. callCount := 0
  2549. mockClient := &mockClient{
  2550. getItemsFunc: func(vaultQuery string) ([]onepassword.Item, error) {
  2551. callCount++
  2552. return nil, tc.err
  2553. },
  2554. }
  2555. retryClient := newRetryClient(mockClient)
  2556. _, err := retryClient.GetItems("test-vault")
  2557. if tc.expectErr && err == nil {
  2558. t.Errorf("expected error but got none")
  2559. }
  2560. if !tc.expectErr && err != nil {
  2561. t.Errorf("expected no error but got: %v", err)
  2562. }
  2563. expectedCalls := 1
  2564. if tc.shouldRetry {
  2565. expectedCalls = 3 // Initial call + 2 retries (3 steps configured in retry backoff)
  2566. }
  2567. if callCount < expectedCalls {
  2568. t.Errorf("expected at least %d calls but got %d", expectedCalls, callCount)
  2569. }
  2570. })
  2571. }
  2572. }
  2573. func TestIs403AuthError(t *testing.T) {
  2574. tests := []struct {
  2575. name string
  2576. err error
  2577. expected bool
  2578. }{
  2579. {
  2580. name: "nil error",
  2581. err: nil,
  2582. expected: false,
  2583. },
  2584. {
  2585. name: "403 auth error",
  2586. err: errors.New("status 403: Authorization failed"),
  2587. expected: true,
  2588. },
  2589. {
  2590. name: "other error",
  2591. err: errors.New("status 500: Internal Server Error"),
  2592. expected: false,
  2593. },
  2594. {
  2595. name: "partial match",
  2596. err: errors.New("403: some other message"),
  2597. expected: false,
  2598. },
  2599. }
  2600. for _, tc := range tests {
  2601. t.Run(tc.name, func(t *testing.T) {
  2602. result := is403AuthError(tc.err)
  2603. if result != tc.expected {
  2604. t.Errorf("expected %v but got %v", tc.expected, result)
  2605. }
  2606. })
  2607. }
  2608. }