@@ -422,3 +422,140 @@ func validateMicroflowFromMDL(t *testing.T, input string) []string {
422422
423423 return ValidateMicroflowBody (stmt )
424424}
425+
426+ // TestAssociationNavParsing verifies that $Var/Module.Assoc/Attr parses as
427+ // AttributePathExpr (not nested BinaryExpr with "/" operator).
428+ // Issue #120: extra spaces around path separators.
429+ func TestAssociationNavParsing (t * testing.T ) {
430+ input := `CREATE MICROFLOW Test.MF_Nav()
431+ RETURNS String AS $Result
432+ BEGIN
433+ DECLARE $CustName String = $Order/Test.Order_Customer/Name;
434+ RETURN $CustName;
435+ END;`
436+
437+ prog , errs := visitor .Build (input )
438+ if len (errs ) > 0 {
439+ t .Fatalf ("Parse error: %v" , errs [0 ])
440+ }
441+
442+ stmt := prog .Statements [0 ].(* ast.CreateMicroflowStmt )
443+ declStmt := stmt .Body [0 ].(* ast.DeclareStmt )
444+
445+ // The expression should be an AttributePathExpr, not a BinaryExpr
446+ pathExpr , ok := declStmt .InitialValue .(* ast.AttributePathExpr )
447+ if ! ok {
448+ t .Fatalf ("Expected AttributePathExpr, got %T" , declStmt .InitialValue )
449+ }
450+
451+ if pathExpr .Variable != "Order" {
452+ t .Errorf ("Variable = %q, want %q" , pathExpr .Variable , "Order" )
453+ }
454+ if len (pathExpr .Path ) != 2 {
455+ t .Fatalf ("Path length = %d, want 2" , len (pathExpr .Path ))
456+ }
457+ if pathExpr .Path [0 ] != "Test.Order_Customer" {
458+ t .Errorf ("Path[0] = %q, want %q" , pathExpr .Path [0 ], "Test.Order_Customer" )
459+ }
460+ if pathExpr .Path [1 ] != "Name" {
461+ t .Errorf ("Path[1] = %q, want %q" , pathExpr .Path [1 ], "Name" )
462+ }
463+
464+ // Serialized form should have no extra spaces
465+ got := expressionToString (pathExpr )
466+ want := "$Order/Test.Order_Customer/Name"
467+ if got != want {
468+ t .Errorf ("expressionToString() = %q, want %q" , got , want )
469+ }
470+ }
471+
472+ // TestResolveAssociationPaths verifies that resolveAssociationPaths inserts
473+ // the target entity after an association segment.
474+ // Issue #120: missing target entity qualifier.
475+ func TestResolveAssociationPaths (t * testing.T ) {
476+ tests := []struct {
477+ name string
478+ path []string
479+ want []string
480+ }{
481+ {
482+ name : "simple_attribute" ,
483+ path : []string {"Name" },
484+ want : []string {"Name" },
485+ },
486+ {
487+ name : "assoc_then_attr" ,
488+ path : []string {"Test.Order_Customer" , "Name" },
489+ want : []string {"Test.Order_Customer" , "Test.Customer" , "Name" },
490+ },
491+ {
492+ name : "already_has_target_entity" ,
493+ path : []string {"Test.Order_Customer" , "Test.Customer" , "Name" },
494+ want : []string {"Test.Order_Customer" , "Test.Customer" , "Name" },
495+ },
496+ {
497+ name : "assoc_at_end" ,
498+ path : []string {"Test.Order_Customer" },
499+ want : []string {"Test.Order_Customer" },
500+ },
501+ }
502+
503+ for _ , tt := range tests {
504+ t .Run (tt .name , func (t * testing.T ) {
505+ fb := & flowBuilder {
506+ reader : nil , // nil reader → no resolution, path unchanged
507+ }
508+ got := fb .resolvePathSegments (tt .path )
509+
510+ // With nil reader, all paths should be unchanged
511+ if len (got ) != len (tt .path ) {
512+ t .Errorf ("resolvePathSegments() length = %d, want %d" , len (got ), len (tt .path ))
513+ }
514+ })
515+ }
516+ }
517+
518+ // TestExprToStringNoSpaces verifies that association navigation expressions
519+ // produce no extra spaces around separators after parsing.
520+ // Issue #120: generated $Order / Module.Assoc / Name instead of $Order/Module.Assoc/Name
521+ func TestExprToStringNoSpaces (t * testing.T ) {
522+ tests := []struct {
523+ name string
524+ expr ast.Expression
525+ want string
526+ }{
527+ {
528+ name : "simple_path" ,
529+ expr : & ast.AttributePathExpr {
530+ Variable : "Order" ,
531+ Path : []string {"OrderNumber" },
532+ },
533+ want : "$Order/OrderNumber" ,
534+ },
535+ {
536+ name : "assoc_path" ,
537+ expr : & ast.AttributePathExpr {
538+ Variable : "Order" ,
539+ Path : []string {"Test.Order_Customer" , "Name" },
540+ },
541+ want : "$Order/Test.Order_Customer/Name" ,
542+ },
543+ {
544+ name : "multi_segment_path" ,
545+ expr : & ast.AttributePathExpr {
546+ Variable : "Invoice" ,
547+ Path : []string {"Billing.Invoice_Order" , "Billing.Order_Customer" , "Name" },
548+ },
549+ want : "$Invoice/Billing.Invoice_Order/Billing.Order_Customer/Name" ,
550+ },
551+ }
552+
553+ for _ , tt := range tests {
554+ t .Run (tt .name , func (t * testing.T ) {
555+ got := expressionToString (tt .expr )
556+ if got != tt .want {
557+ t .Errorf ("expressionToString() = %q, want %q" , got , tt .want )
558+ }
559+ })
560+ }
561+ }
0 commit comments