diff --git a/mdl-examples/bug-tests/595-published-odata-entitytypepointer.mdl b/mdl-examples/bug-tests/595-published-odata-entitytypepointer.mdl new file mode 100644 index 00000000..0d83e71f --- /dev/null +++ b/mdl-examples/bug-tests/595-published-odata-entitytypepointer.mdl @@ -0,0 +1,38 @@ +-- Bug test: published OData service EntitySet must carry EntityTypePointer. +-- Issue #595: serializePublishedODataService keyed its entityTypeIDMap by +-- ExposedName but looked it up by qualified entity name, so the resolved +-- ID was always empty and ODataPublish$EntitySet.EntityTypePointer was +-- never written. Studio Pro's EntitySet.Check then dereferenced null and +-- aborted the entire project checker with a NullReferenceException. +-- +-- The Go unit test in sdk/mpr/writer_odata_test.go (TestSerializePublishedODataService) +-- asserts EntityTypePointer matches the EntityType's $ID. This MDL script +-- is the end-to-end fixture: applying it to a project must yield a project +-- that opens in Studio Pro without the EntitySet.Check NRE. +-- +-- Run with: +-- mxcli exec mdl-examples/bug-tests/595-published-odata-entitytypepointer.mdl -p .mpr +-- then open .mpr in Studio Pro and confirm no NRE in the project checker. +create module bug595; + +create persistent entity bug595.Customer ( + Name: string(200), + Email: string(200) +); + +create odata service bug595.CustomerAPI ( + path: '/odata/customers', + version: '1.0.0', + ODataVersion: OData4, + namespace: 'bug595.Customers' +) +authentication basic +{ + publish entity bug595.Customer as 'Customers' ( + ReadMode: source, + InsertMode: source, + UpdateMode: source, + DeleteMode: not_supported + ) + expose (*); +}; diff --git a/sdk/mpr/writer_odata.go b/sdk/mpr/writer_odata.go index 8fc8b8aa..2435ef8f 100644 --- a/sdk/mpr/writer_odata.go +++ b/sdk/mpr/writer_odata.go @@ -209,8 +209,13 @@ func (w *Writer) serializePublishedODataService(svc *model.PublishedODataService authTypes = append(authTypes, at) } - // Serialize entity types and build ID map for entity set pointers - entityTypeIDMap := make(map[string]string) // ExposedName -> entity type ID + // Serialize entity types and build ID map for entity set pointers. + // Issue #595: key by qualified entity name (et.Entity), not ExposedName. + // PublishedEntitySet.EntityTypeName holds the qualified name, so keying + // by ExposedName made the lookup return "" and EntityTypePointer was + // never written. Studio Pro's EntitySet.Check then NREs dereferencing + // the missing pointer and aborts the whole project checker. + entityTypeIDMap := make(map[string]string) // qualified entity name -> entity type ID entityTypes := bson.A{} for _, et := range svc.EntityTypes { etID := string(et.ID) @@ -218,7 +223,7 @@ func (w *Writer) serializePublishedODataService(svc *model.PublishedODataService etID = generateUUID() et.ID = model.ID(etID) } - entityTypeIDMap[et.ExposedName] = etID + entityTypeIDMap[et.Entity] = etID entityTypes = append(entityTypes, serializePublishedEntityType(et)) } diff --git a/sdk/mpr/writer_odata_test.go b/sdk/mpr/writer_odata_test.go index db4d94f7..3e0d088c 100644 --- a/sdk/mpr/writer_odata_test.go +++ b/sdk/mpr/writer_odata_test.go @@ -7,6 +7,7 @@ import ( "github.com/mendixlabs/mxcli/model" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" ) func TestSerializeConsumedODataService(t *testing.T) { @@ -158,7 +159,7 @@ func TestSerializePublishedODataService(t *testing.T) { AuthenticationTypes: []string{"Basic", "Session"}, EntityTypes: []*model.PublishedEntityType{ { - BaseElement: model.BaseElement{ID: "et-1"}, + BaseElement: model.BaseElement{ID: "11111111-1111-1111-1111-111111111111"}, Entity: "MyModule.Customer", ExposedName: "Customers", Members: []*model.PublishedMember{ @@ -282,6 +283,20 @@ func TestSerializePublishedODataService(t *testing.T) { assertField(t, esMap, "$Type", "ODataPublish$EntitySet") assertField(t, esMap, "ExposedName", "Customers") + // Issue #595: EntityTypePointer must reference the owning EntityType. + // Without it, Studio Pro's EntitySet.Check NREs (it can't navigate from + // the set to its type). The map lookup in serializePublishedODataService + // was previously keyed by ExposedName instead of the qualified entity + // name, so the resolved ID was always empty and the pointer was omitted. + etID := etMap["$ID"].(primitive.Binary) + esPointer, ok := esMap["EntityTypePointer"].(primitive.Binary) + if !ok { + t.Fatalf("EntityTypePointer: expected primitive.Binary, got %T (%v)", esMap["EntityTypePointer"], esMap["EntityTypePointer"]) + } + if string(esPointer.Data) != string(etID.Data) { + t.Errorf("EntityTypePointer = %x, want %x (entity type $ID)", esPointer.Data, etID.Data) + } + if v, ok := esMap["UsePaging"].(bool); !ok || !v { t.Errorf("UsePaging: expected true, got %v", esMap["UsePaging"]) }