@@ -25,6 +25,8 @@ import (
2525 "github.com/stretchr/testify/assert"
2626 "github.com/stretchr/testify/require"
2727
28+ kc "github.com/docker/secrets-engine/store/keychain/internal/go-keychain"
29+
2830 "github.com/docker/secrets-engine/store"
2931 "github.com/docker/secrets-engine/store/mocks"
3032)
@@ -150,6 +152,82 @@ func TestMacosKeychain(t *testing.T) {
150152 })
151153}
152154
155+ func TestUpsert (t * testing.T ) {
156+ var (
157+ serviceName = uuid .NewString ()
158+ serviceGroup = "com.test.testing"
159+ )
160+ ks := keychainStore [* mocks.MockCredential ]{
161+ serviceGroup : serviceGroup ,
162+ serviceName : serviceName ,
163+ factory : func (_ context.Context , _ store.ID ) * mocks.MockCredential {
164+ return & mocks.MockCredential {}
165+ },
166+ }
167+
168+ t .Run ("inserts when credential does not exist" , func (t * testing.T ) {
169+ id := store .MustParseID (serviceGroup + "/" + serviceName + "/" + uuid .NewString ())
170+ t .Cleanup (func () {
171+ assert .NoError (t , ks .Delete (t .Context (), id ))
172+ })
173+
174+ secret := & mocks.MockCredential {
175+ Username : "alice" ,
176+ Password : "alice-password" ,
177+ }
178+ require .NoError (t , ks .Upsert (t .Context (), id , secret ))
179+
180+ got , err := ks .Get (t .Context (), id )
181+ require .NoError (t , err )
182+ actual := got .(* mocks.MockCredential )
183+ actual .Attributes = nil
184+ assert .Equal (t , secret .Username , actual .Username )
185+ assert .Equal (t , secret .Password , actual .Password )
186+ })
187+
188+ t .Run ("overwrites an existing credential" , func (t * testing.T ) {
189+ id := store .MustParseID (serviceGroup + "/" + serviceName + "/" + uuid .NewString ())
190+ t .Cleanup (func () {
191+ assert .NoError (t , ks .Delete (t .Context (), id ))
192+ })
193+
194+ original := & mocks.MockCredential {
195+ Username : "bob" ,
196+ Password : "original-password" ,
197+ }
198+ require .NoError (t , ks .Save (t .Context (), id , original ))
199+
200+ updated := & mocks.MockCredential {
201+ Username : "bob" ,
202+ Password : "updated-password" ,
203+ }
204+ require .NoError (t , ks .Upsert (t .Context (), id , updated ))
205+
206+ got , err := ks .Get (t .Context (), id )
207+ require .NoError (t , err )
208+ actual := got .(* mocks.MockCredential )
209+ actual .Attributes = nil
210+ assert .Equal (t , updated .Username , actual .Username )
211+ assert .Equal (t , updated .Password , actual .Password )
212+ })
213+
214+ t .Run ("save returns duplicate item error when credential already exists" , func (t * testing.T ) {
215+ id := store .MustParseID (serviceGroup + "/" + serviceName + "/" + uuid .NewString ())
216+ t .Cleanup (func () {
217+ assert .NoError (t , ks .Delete (t .Context (), id ))
218+ })
219+
220+ secret := & mocks.MockCredential {
221+ Username : "charlie" ,
222+ Password : "charlie-password" ,
223+ }
224+ require .NoError (t , ks .Save (t .Context (), id , secret ))
225+
226+ err := ks .Save (t .Context (), id , secret )
227+ require .ErrorIs (t , err , kc .ErrorDuplicateItem )
228+ })
229+ }
230+
153231func TestConvertAttributes (t * testing.T ) {
154232 t .Run ("can convert attributes map into map of any" , func (t * testing.T ) {
155233 attributes := map [string ]any {
0 commit comments