Skip to content

Commit 89263ae

Browse files
authored
Merge pull request #261 from 1Password/jill/add-e2e-test-for-state-management
Add tests for state management
2 parents e6cedcc + 6a90dec commit 89263ae

File tree

4 files changed

+542
-49
lines changed

4 files changed

+542
-49
lines changed

test/e2e/item_data_source_test.go

Lines changed: 236 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
package integration
22

33
import (
4+
"context"
45
"fmt"
6+
"maps"
57
"regexp"
68
"testing"
79

8-
op "github.com/1Password/connect-sdk-go/onepassword"
10+
"github.com/google/uuid"
911
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
1012
"github.com/hashicorp/terraform-plugin-testing/terraform"
1113

14+
"github.com/1Password/terraform-provider-onepassword/v2/internal/onepassword/model"
1215
tfconfig "github.com/1Password/terraform-provider-onepassword/v2/test/e2e/terraform/config"
16+
"github.com/1Password/terraform-provider-onepassword/v2/test/e2e/utils/attributes"
17+
"github.com/1Password/terraform-provider-onepassword/v2/test/e2e/utils/checks"
18+
"github.com/1Password/terraform-provider-onepassword/v2/test/e2e/utils/client"
19+
"github.com/1Password/terraform-provider-onepassword/v2/test/e2e/utils/sections"
1320
"github.com/1Password/terraform-provider-onepassword/v2/test/e2e/utils/ssh"
21+
uuidutil "github.com/1Password/terraform-provider-onepassword/v2/test/e2e/utils/uuid"
1422
)
1523

1624
const testVaultID = "bbucuyq2nn4fozygwttxwizpcy"
@@ -27,8 +35,8 @@ type testItem struct {
2735
Attrs map[string]string
2836
}
2937

30-
var testItems = map[op.ItemCategory]testItem{
31-
op.Login: {
38+
var testItems = map[model.ItemCategory]testItem{
39+
model.Login: {
3240
Title: "Test Login",
3341
UUID: "5axoqbjhbx3u7wqmersrg6qnqy",
3442
Attrs: map[string]string{
@@ -38,15 +46,15 @@ var testItems = map[op.ItemCategory]testItem{
3846
"url": "www.example.com",
3947
},
4048
},
41-
op.Password: {
49+
model.Password: {
4250
Title: "Test Password",
4351
UUID: "axoqeauq7ilndgdpimb4j4dwhi",
4452
Attrs: map[string]string{
4553
"category": "password",
4654
"password": "testPassword",
4755
},
4856
},
49-
op.Database: {
57+
model.Database: {
5058
Title: "Test Database",
5159
UUID: "ck6mbmf3yjps6gk5qldnx4frni",
5260
Attrs: map[string]string{
@@ -58,15 +66,15 @@ var testItems = map[op.ItemCategory]testItem{
5866
"type": "mysql",
5967
},
6068
},
61-
op.SecureNote: {
69+
model.SecureNote: {
6270
Title: "Test Secure Note",
6371
UUID: "5xbca3eblv5kxkszrbuhdame4a",
6472
Attrs: map[string]string{
6573
"category": "secure_note",
6674
"note_value": "This is a test secure note for terraform-provider-onepassword",
6775
},
6876
},
69-
op.Document: {
77+
model.Document: {
7078
Title: "Test Document",
7179
UUID: "p6uyugpmxo6zcxo5fdfctet7xa",
7280
Attrs: map[string]string{
@@ -76,7 +84,7 @@ var testItems = map[op.ItemCategory]testItem{
7684
"file.0.content_base64": "VGhpcyBpcyBhIHRlc3Q=",
7785
},
7886
},
79-
op.SSHKey: {
87+
model.SSHKey: {
8088
Title: "Test SSH Key",
8189
UUID: "5dbnxvhcknslz4mcaz7lobzt6i",
8290
Attrs: map[string]string{
@@ -100,15 +108,15 @@ func TestAccItemDataSource(t *testing.T) {
100108
}
101109

102110
itemTypes := []struct {
103-
category op.ItemCategory
111+
category model.ItemCategory
104112
name string
105113
}{
106-
{op.Login, "Login"},
107-
{op.Password, "Password"},
108-
{op.Database, "Database"},
109-
{op.SecureNote, "SecureNote"},
110-
{op.Document, "Document"},
111-
{op.SSHKey, "SSHKey"},
114+
{model.Login, "Login"},
115+
{model.Password, "Password"},
116+
{model.Database, "Database"},
117+
{model.SecureNote, "SecureNote"},
118+
{model.Document, "Document"},
119+
{model.SSHKey, "SSHKey"},
112120
}
113121

114122
var testCases []itemDataSourceTestCase
@@ -207,3 +215,216 @@ func TestAccItemDataSource_NotFound(t *testing.T) {
207215
})
208216
}
209217
}
218+
219+
func TestAccItemDataSource_DetectManualChanges(t *testing.T) {
220+
// Generate unique identifier for this test run to avoid conflicts in parallel execution
221+
uniqueID := uuid.New().String()
222+
var itemUUID string
223+
224+
item := testItemsToCreate[model.Login]
225+
initialAttrs := maps.Clone(item.Attrs)
226+
initialAttrs["title"] = addUniqueIDToTitle(initialAttrs["title"].(string), uniqueID)
227+
initialAttrs["section"] = sections.MapSections([]sections.TestSection{
228+
{
229+
Label: "Original Section",
230+
Fields: []sections.TestField{
231+
{Label: "Original Field 1", Value: "original value 1", Type: "STRING"},
232+
{Label: "Original Field 2", Value: "original value 2", Type: "EMAIL"},
233+
},
234+
},
235+
})
236+
237+
updatedAttrs := maps.Clone(testItemsUpdatedAttrs[model.Login])
238+
updatedAttrs["title"] = initialAttrs["title"]
239+
updatedAttrs["section"] = sections.MapSections([]sections.TestSection{
240+
{
241+
Label: "Updated Section",
242+
Fields: []sections.TestField{
243+
{Label: "New Field", Value: "new value", Type: "URL"},
244+
},
245+
},
246+
})
247+
248+
removedAttrs := map[string]any{
249+
"title": initialAttrs["title"],
250+
"category": "login",
251+
"url": []string{},
252+
"tags": []string{},
253+
"section": []map[string]any{},
254+
}
255+
256+
// Initial data source read checks
257+
initialReadChecks := []resource.TestCheckFunc{
258+
logStep(t, "INITIAL_READ"),
259+
uuidutil.CaptureItemUUID(t, "data.onepassword_item.test_item", &itemUUID),
260+
}
261+
bcInitial := checks.BuildItemChecks("data.onepassword_item.test_item", initialAttrs)
262+
initialReadChecks = append(initialReadChecks, bcInitial...)
263+
264+
// Build check function to manually update the item
265+
updateItemOutsideTerraform := func() resource.TestCheckFunc {
266+
return func(s *terraform.State) error {
267+
t.Log("MANUALLY_UPDATE_ITEM")
268+
ctx := context.Background()
269+
270+
client, err := client.CreateTestClient(ctx)
271+
if err != nil {
272+
return fmt.Errorf("failed to create client: %w", err)
273+
}
274+
275+
currentItem := &model.Item{
276+
ID: itemUUID,
277+
VaultID: testVaultID,
278+
Category: model.Login,
279+
}
280+
updatedItem := attributes.BuildUpdatedItemAttrs(currentItem, updatedAttrs)
281+
282+
_, err = client.UpdateItem(ctx, updatedItem, testVaultID)
283+
if err != nil {
284+
return fmt.Errorf("failed to update item: %w", err)
285+
}
286+
287+
return nil
288+
}
289+
}
290+
291+
// Build checks for updated data source read
292+
updatedReadChecks := []resource.TestCheckFunc{
293+
logStep(t, "READ_AFTER_UPDATE"),
294+
}
295+
bcUpdated := checks.BuildItemChecks("data.onepassword_item.test_item", updatedAttrs)
296+
updatedReadChecks = append(updatedReadChecks, bcUpdated...)
297+
298+
// Build check function to manually remove all fields
299+
removeFieldsOutsideTerraform := func() resource.TestCheckFunc {
300+
return func(s *terraform.State) error {
301+
t.Log("MANUALLY_REMOVE_ALL_FIELDS")
302+
ctx := context.Background()
303+
304+
client, err := client.CreateTestClient(ctx)
305+
if err != nil {
306+
return fmt.Errorf("failed to create client: %w", err)
307+
}
308+
309+
strippedItem := &model.Item{
310+
ID: itemUUID,
311+
Title: removedAttrs["title"].(string),
312+
VaultID: testVaultID,
313+
Category: model.Login,
314+
Tags: []string{},
315+
URLs: []model.ItemURL{
316+
{URL: "", Primary: true},
317+
},
318+
Sections: []model.ItemSection{},
319+
Fields: []model.ItemField{},
320+
}
321+
322+
_, err = client.UpdateItem(ctx, strippedItem, testVaultID)
323+
if err != nil {
324+
return fmt.Errorf("failed to remove fields: %w", err)
325+
}
326+
327+
return nil
328+
}
329+
}
330+
331+
// Build checks for reading after field removal
332+
removedFieldsReadChecks := []resource.TestCheckFunc{
333+
logStep(t, "READ_AFTER_REMOVAL"),
334+
}
335+
bcRemoved := checks.BuildItemChecks("data.onepassword_item.test_item", removedAttrs)
336+
removedFieldsReadChecks = append(removedFieldsReadChecks, bcRemoved...)
337+
338+
// Verify that username is either not present (SDK) or empty (Connect)
339+
removedFieldsReadChecks = append(removedFieldsReadChecks, resource.TestCheckFunc(func(s *terraform.State) error {
340+
item, ok := s.RootModule().Resources["data.onepassword_item.test_item"]
341+
if !ok {
342+
return fmt.Errorf("resource not found in state")
343+
}
344+
345+
username, exists := item.Primary.Attributes["username"]
346+
if exists {
347+
// If username exists, it should be empty (Connect behavior)
348+
if username != "" {
349+
return fmt.Errorf("expected username to be empty or not present, got %q", username)
350+
}
351+
}
352+
// If username doesn't exist, that's also valid (SDK behavior)
353+
return nil
354+
}))
355+
356+
resource.Test(t, resource.TestCase{
357+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
358+
Steps: []resource.TestStep{
359+
// Create item using resource
360+
{
361+
Config: tfconfig.CreateConfigBuilder()(
362+
tfconfig.ProviderConfig(),
363+
tfconfig.ItemResourceConfig(testVaultID, initialAttrs),
364+
),
365+
},
366+
// Read item with data source
367+
{
368+
Config: tfconfig.CreateConfigBuilder()(
369+
tfconfig.ProviderConfig(),
370+
tfconfig.ItemResourceConfig(testVaultID, initialAttrs),
371+
tfconfig.ItemDataSourceConfig(map[string]string{
372+
"vault": testVaultID,
373+
"title": fmt.Sprintf("%v", initialAttrs["title"]),
374+
}),
375+
),
376+
Check: resource.ComposeAggregateTestCheckFunc(initialReadChecks...),
377+
},
378+
// Manually update item
379+
{
380+
Config: tfconfig.CreateConfigBuilder()(
381+
tfconfig.ProviderConfig(),
382+
tfconfig.ItemResourceConfig(testVaultID, initialAttrs),
383+
tfconfig.ItemDataSourceConfig(map[string]string{
384+
"vault": testVaultID,
385+
"title": fmt.Sprintf("%v", initialAttrs["title"]),
386+
}),
387+
),
388+
Check: updateItemOutsideTerraform(),
389+
ExpectNonEmptyPlan: true,
390+
},
391+
// Data source should read the updated values
392+
{
393+
Config: tfconfig.CreateConfigBuilder()(
394+
tfconfig.ProviderConfig(),
395+
tfconfig.ItemResourceConfig(testVaultID, initialAttrs),
396+
tfconfig.ItemDataSourceConfig(map[string]string{
397+
"vault": testVaultID,
398+
"title": fmt.Sprintf("%v", initialAttrs["title"]),
399+
}),
400+
),
401+
Check: resource.ComposeAggregateTestCheckFunc(updatedReadChecks...),
402+
},
403+
// Manually remove fields
404+
{
405+
Config: tfconfig.CreateConfigBuilder()(
406+
tfconfig.ProviderConfig(),
407+
tfconfig.ItemResourceConfig(testVaultID, initialAttrs),
408+
tfconfig.ItemDataSourceConfig(map[string]string{
409+
"vault": testVaultID,
410+
"title": fmt.Sprintf("%v", initialAttrs["title"]),
411+
}),
412+
),
413+
Check: removeFieldsOutsideTerraform(),
414+
ExpectNonEmptyPlan: true,
415+
},
416+
// Data source should read the removed fields
417+
{
418+
Config: tfconfig.CreateConfigBuilder()(
419+
tfconfig.ProviderConfig(),
420+
tfconfig.ItemResourceConfig(testVaultID, initialAttrs),
421+
tfconfig.ItemDataSourceConfig(map[string]string{
422+
"vault": testVaultID,
423+
"title": fmt.Sprintf("%v", initialAttrs["title"]),
424+
}),
425+
),
426+
Check: resource.ComposeAggregateTestCheckFunc(removedFieldsReadChecks...),
427+
},
428+
},
429+
})
430+
}

0 commit comments

Comments
 (0)