@@ -242,6 +242,149 @@ func TestParseEdmxEmpty(t *testing.T) {
242242 }
243243}
244244
245+ const testCapabilitiesMetadata = `<?xml version="1.0" encoding="utf-8"?>
246+ <edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
247+ <edmx:DataServices>
248+ <Schema Namespace="DefaultNamespace" xmlns="http://docs.oasis-open.org/odata/ns/edm">
249+ <EntityType Name="Order">
250+ <Key><PropertyRef Name="OrderId" /></Key>
251+ <Property Name="OrderId" Type="Edm.Int64" Nullable="false">
252+ <Annotation Term="Org.OData.Core.V1.Computed" Bool="true" />
253+ </Property>
254+ <Property Name="OrderNumber" Type="Edm.String" MaxLength="32">
255+ <Annotation Term="Org.OData.Core.V1.Immutable" Bool="true" />
256+ </Property>
257+ <Property Name="CustomerName" Type="Edm.String" MaxLength="200" />
258+ </EntityType>
259+ <EntityType Name="OrderLine">
260+ <Key><PropertyRef Name="LineId" /></Key>
261+ <Property Name="LineId" Type="Edm.Int64" Nullable="false" />
262+ </EntityType>
263+ <EntityContainer Name="Container">
264+ <EntitySet Name="Orders" EntityType="DefaultNamespace.Order">
265+ <Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
266+ <Record>
267+ <PropertyValue Property="Insertable" Bool="true" />
268+ <PropertyValue Property="NonInsertableProperties">
269+ <Collection>
270+ <PropertyPath>OrderId</PropertyPath>
271+ </Collection>
272+ </PropertyValue>
273+ <PropertyValue Property="NonInsertableNavigationProperties">
274+ <Collection>
275+ <NavigationPropertyPath>Lines</NavigationPropertyPath>
276+ </Collection>
277+ </PropertyValue>
278+ </Record>
279+ </Annotation>
280+ <Annotation Term="Org.OData.Capabilities.V1.UpdateRestrictions">
281+ <Record>
282+ <PropertyValue Property="Updatable" Bool="true" />
283+ <PropertyValue Property="NonUpdatableProperties">
284+ <Collection>
285+ <PropertyPath>OrderId</PropertyPath>
286+ <PropertyPath>OrderNumber</PropertyPath>
287+ </Collection>
288+ </PropertyValue>
289+ </Record>
290+ </Annotation>
291+ <Annotation Term="Org.OData.Capabilities.V1.DeleteRestrictions">
292+ <Record><PropertyValue Property="Deletable" Bool="true" /></Record>
293+ </Annotation>
294+ </EntitySet>
295+ <EntitySet Name="OrderLines" EntityType="DefaultNamespace.OrderLine" />
296+ </EntityContainer>
297+ </Schema>
298+ </edmx:DataServices>
299+ </edmx:Edmx>`
300+
301+ func TestParseEdmxCapabilityAnnotations (t * testing.T ) {
302+ doc , err := ParseEdmx (testCapabilitiesMetadata )
303+ if err != nil {
304+ t .Fatalf ("ParseEdmx failed: %v" , err )
305+ }
306+
307+ // Find the Orders entity set.
308+ var orders * EdmEntitySet
309+ for _ , es := range doc .EntitySets {
310+ if es .Name == "Orders" {
311+ orders = es
312+ }
313+ }
314+ if orders == nil {
315+ t .Fatal ("Orders entity set not found" )
316+ }
317+
318+ if orders .Insertable == nil || ! * orders .Insertable {
319+ t .Errorf ("Orders.Insertable = %v, want true" , orders .Insertable )
320+ }
321+ if orders .Updatable == nil || ! * orders .Updatable {
322+ t .Errorf ("Orders.Updatable = %v, want true" , orders .Updatable )
323+ }
324+ if orders .Deletable == nil || ! * orders .Deletable {
325+ t .Errorf ("Orders.Deletable = %v, want true" , orders .Deletable )
326+ }
327+
328+ wantNonIns := []string {"OrderId" }
329+ if ! stringSliceEqual (orders .NonInsertableProperties , wantNonIns ) {
330+ t .Errorf ("NonInsertableProperties = %v, want %v" , orders .NonInsertableProperties , wantNonIns )
331+ }
332+ wantNonUpd := []string {"OrderId" , "OrderNumber" }
333+ if ! stringSliceEqual (orders .NonUpdatableProperties , wantNonUpd ) {
334+ t .Errorf ("NonUpdatableProperties = %v, want %v" , orders .NonUpdatableProperties , wantNonUpd )
335+ }
336+ wantNonInsNav := []string {"Lines" }
337+ if ! stringSliceEqual (orders .NonInsertableNavigationProperties , wantNonInsNav ) {
338+ t .Errorf ("NonInsertableNavigationProperties = %v, want %v" , orders .NonInsertableNavigationProperties , wantNonInsNav )
339+ }
340+
341+ // OrderLines has no annotations → all flags unset.
342+ var lines * EdmEntitySet
343+ for _ , es := range doc .EntitySets {
344+ if es .Name == "OrderLines" {
345+ lines = es
346+ }
347+ }
348+ if lines == nil {
349+ t .Fatal ("OrderLines entity set not found" )
350+ }
351+ if lines .Insertable != nil || lines .Updatable != nil || lines .Deletable != nil {
352+ t .Errorf ("OrderLines should have nil capability flags, got Insertable=%v Updatable=%v Deletable=%v" ,
353+ lines .Insertable , lines .Updatable , lines .Deletable )
354+ }
355+
356+ // Per-property Computed/Immutable annotations.
357+ order := doc .FindEntityType ("DefaultNamespace.Order" )
358+ if order == nil {
359+ t .Fatal ("Order entity type not found" )
360+ }
361+ propByName := map [string ]* EdmProperty {}
362+ for _ , p := range order .Properties {
363+ propByName [p .Name ] = p
364+ }
365+ if ! propByName ["OrderId" ].Computed {
366+ t .Errorf ("OrderId.Computed = false, want true" )
367+ }
368+ if ! propByName ["OrderNumber" ].Immutable {
369+ t .Errorf ("OrderNumber.Immutable = false, want true" )
370+ }
371+ if propByName ["CustomerName" ].Computed || propByName ["CustomerName" ].Immutable {
372+ t .Errorf ("CustomerName should have no capability flags" )
373+ }
374+ }
375+
376+ func stringSliceEqual (a , b []string ) bool {
377+ if len (a ) != len (b ) {
378+ return false
379+ }
380+ for i := range a {
381+ if a [i ] != b [i ] {
382+ return false
383+ }
384+ }
385+ return true
386+ }
387+
245388func TestResolveNavType (t * testing.T ) {
246389 tests := []struct {
247390 input string
0 commit comments