|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | + |
| 3 | +package mpr |
| 4 | + |
| 5 | +import ( |
| 6 | + "database/sql" |
| 7 | + "path/filepath" |
| 8 | + "testing" |
| 9 | + |
| 10 | + "github.com/mendixlabs/mxcli/model" |
| 11 | + "go.mongodb.org/mongo-driver/bson" |
| 12 | + _ "modernc.org/sqlite" |
| 13 | +) |
| 14 | + |
| 15 | +// JSON structures live inside a module but may be nested in subfolders. |
| 16 | +// GetJsonStructureByQualifiedName must resolve container IDs through the |
| 17 | +// folder hierarchy up to the owning module; otherwise addRestCallAction |
| 18 | +// silently defaults SingleObject=false and produces invalid REST-call |
| 19 | +// roundtrips on projects that organise their JSON structures in |
| 20 | +// folders. |
| 21 | +func TestGetJsonStructureByQualifiedName_ResolvesThroughFolders(t *testing.T) { |
| 22 | + dbPath := filepath.Join(t.TempDir(), "test.mpr") |
| 23 | + db, err := sql.Open("sqlite", dbPath) |
| 24 | + if err != nil { |
| 25 | + t.Fatalf("open: %v", err) |
| 26 | + } |
| 27 | + t.Cleanup(func() { _ = db.Close() }) |
| 28 | + |
| 29 | + if _, err := db.Exec(` |
| 30 | + CREATE TABLE Unit ( |
| 31 | + UnitID BLOB PRIMARY KEY NOT NULL, |
| 32 | + ContainerID BLOB, |
| 33 | + ContainmentName TEXT, |
| 34 | + TreeConflict LONG, |
| 35 | + ContentsHash TEXT, |
| 36 | + ContentsConflicts TEXT, |
| 37 | + Contents BLOB |
| 38 | + ) |
| 39 | + `); err != nil { |
| 40 | + t.Fatalf("create Unit: %v", err) |
| 41 | + } |
| 42 | + |
| 43 | + reader := &Reader{db: db, version: MPRVersionV1} |
| 44 | + |
| 45 | + moduleID := "11111111-1111-1111-1111-111111111111" |
| 46 | + folderID := "22222222-2222-2222-2222-222222222222" |
| 47 | + jsID := "33333333-3333-3333-3333-333333333333" |
| 48 | + otherModuleID := "44444444-4444-4444-4444-444444444444" |
| 49 | + |
| 50 | + // Module: SBOMModule |
| 51 | + modBSON, _ := bson.Marshal(bson.D{ |
| 52 | + {Key: "$Type", Value: "Projects$ModuleImpl"}, |
| 53 | + {Key: "$ID", Value: idToBsonBinary(moduleID)}, |
| 54 | + {Key: "Name", Value: "SBOMModule"}, |
| 55 | + }) |
| 56 | + if _, err := db.Exec(`INSERT INTO Unit (UnitID, ContainerID, ContainmentName, Contents) VALUES (?, ?, 'Module', ?)`, |
| 57 | + uuidToBlob(moduleID), nil, modBSON); err != nil { |
| 58 | + t.Fatalf("insert module: %v", err) |
| 59 | + } |
| 60 | + |
| 61 | + // Other module (for the negative case) |
| 62 | + otherModBSON, _ := bson.Marshal(bson.D{ |
| 63 | + {Key: "$Type", Value: "Projects$ModuleImpl"}, |
| 64 | + {Key: "$ID", Value: idToBsonBinary(otherModuleID)}, |
| 65 | + {Key: "Name", Value: "OtherModule"}, |
| 66 | + }) |
| 67 | + if _, err := db.Exec(`INSERT INTO Unit (UnitID, ContainerID, ContainmentName, Contents) VALUES (?, ?, 'Module', ?)`, |
| 68 | + uuidToBlob(otherModuleID), nil, otherModBSON); err != nil { |
| 69 | + t.Fatalf("insert other module: %v", err) |
| 70 | + } |
| 71 | + |
| 72 | + // Folder inside SBOMModule |
| 73 | + folderBSON, _ := bson.Marshal(bson.D{ |
| 74 | + {Key: "$Type", Value: "Projects$Folder"}, |
| 75 | + {Key: "$ID", Value: idToBsonBinary(folderID)}, |
| 76 | + {Key: "Name", Value: "Payloads"}, |
| 77 | + }) |
| 78 | + if _, err := db.Exec(`INSERT INTO Unit (UnitID, ContainerID, ContainmentName, Contents) VALUES (?, ?, 'Folder', ?)`, |
| 79 | + uuidToBlob(folderID), uuidToBlob(moduleID), folderBSON); err != nil { |
| 80 | + t.Fatalf("insert folder: %v", err) |
| 81 | + } |
| 82 | + |
| 83 | + // JSON structure nested inside the folder (not the module directly). |
| 84 | + jsBSON, _ := bson.Marshal(bson.D{ |
| 85 | + {Key: "$Type", Value: "JsonStructures$JsonStructure"}, |
| 86 | + {Key: "$ID", Value: idToBsonBinary(jsID)}, |
| 87 | + {Key: "Name", Value: "OrderPayload"}, |
| 88 | + {Key: "Elements", Value: bson.A{ |
| 89 | + int32(2), |
| 90 | + bson.D{ |
| 91 | + {Key: "$Type", Value: "JsonStructures$JsonElement"}, |
| 92 | + {Key: "ExposedName", Value: "Root"}, |
| 93 | + {Key: "ElementType", Value: "Object"}, |
| 94 | + }, |
| 95 | + }}, |
| 96 | + }) |
| 97 | + if _, err := db.Exec(`INSERT INTO Unit (UnitID, ContainerID, ContainmentName, Contents) VALUES (?, ?, 'Document', ?)`, |
| 98 | + uuidToBlob(jsID), uuidToBlob(folderID), jsBSON); err != nil { |
| 99 | + t.Fatalf("insert json structure: %v", err) |
| 100 | + } |
| 101 | + |
| 102 | + // Lookup by owning module name should resolve through the folder. |
| 103 | + js, err := reader.GetJsonStructureByQualifiedName("SBOMModule", "OrderPayload") |
| 104 | + if err != nil { |
| 105 | + t.Fatalf("GetJsonStructureByQualifiedName (folder-nested): %v", err) |
| 106 | + } |
| 107 | + if js == nil { |
| 108 | + t.Fatal("expected non-nil JsonStructure") |
| 109 | + } |
| 110 | + if js.Name != "OrderPayload" { |
| 111 | + t.Errorf("Name = %q, want OrderPayload", js.Name) |
| 112 | + } |
| 113 | + if len(js.Elements) != 1 || js.Elements[0].ElementType != "Object" { |
| 114 | + t.Errorf("Elements = %+v, want one Object element", js.Elements) |
| 115 | + } |
| 116 | + if js.ContainerID != model.ID(folderID) { |
| 117 | + t.Errorf("ContainerID = %q, want folder ID %q", js.ContainerID, folderID) |
| 118 | + } |
| 119 | + |
| 120 | + // Cross-module lookup must fail (the structure belongs to SBOMModule, not OtherModule). |
| 121 | + if _, err := reader.GetJsonStructureByQualifiedName("OtherModule", "OrderPayload"); err == nil { |
| 122 | + t.Error("expected error for wrong-module lookup, got nil") |
| 123 | + } |
| 124 | +} |
0 commit comments