onepassword_test.go 71 KB

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