Skip to content

Commit 097a799

Browse files
authored
Merge pull request #508 from hjotha/submit/jsonstructure-folder-lookup
fix: resolve JSON structure qualified-name lookup through folder hierarchy
2 parents 1eeef12 + 733e10f commit 097a799

2 files changed

Lines changed: 129 additions & 10 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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+
}

sdk/mpr/reader_types.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -180,27 +180,22 @@ func (r *Reader) ListJsonStructures() ([]*types.JsonStructure, error) {
180180
}
181181

182182
// GetJsonStructureByQualifiedName retrieves a JSON structure by its qualified name (Module.Name).
183+
// Resolves folder containment: the stored ContainerID may be a folder inside
184+
// the module, not the module itself, so we map container IDs through the
185+
// module hierarchy before matching on module name.
183186
func (r *Reader) GetJsonStructureByQualifiedName(moduleName, name string) (*types.JsonStructure, error) {
184187
all, err := r.ListJsonStructures()
185188
if err != nil {
186189
return nil, err
187190
}
188191

189-
modules, err := r.ListModules()
192+
moduleMap, err := r.buildContainerModuleNameMap()
190193
if err != nil {
191194
return nil, err
192195
}
193196

194-
moduleID := ""
195-
for _, m := range modules {
196-
if m.Name == moduleName {
197-
moduleID = string(m.ID)
198-
break
199-
}
200-
}
201-
202197
for _, js := range all {
203-
if js.Name == name && (moduleID == "" || string(js.ContainerID) == moduleID) {
198+
if js.Name == name && moduleMap[js.ContainerID] == moduleName {
204199
return js, nil
205200
}
206201
}

0 commit comments

Comments
 (0)