Skip to content

Commit a1ff017

Browse files
authored
Merge pull request #1033 from FirelyTeam/copilot/fix-1032
Add comprehensive tests for large tuple serialization and invocation verification
2 parents 3dee710 + 1d82428 commit a1ff017

File tree

3 files changed

+404
-0
lines changed

3 files changed

+404
-0
lines changed

Cql/CoreTests/Primitives/TypeExtensionsTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,23 @@ public void IsCqlValueTuple_ShouldReturnFalse_WhenTypeIsNotCqlValueTuple()
6868
// Assert
6969
Assert.IsFalse(result);
7070
}
71+
72+
[TestMethod]
73+
public void IsCqlValueTuple_ShouldReturnTrue_WhenTypeIsLargeCqlValueTuple()
74+
{
75+
// Arrange - Test large tuples with more than 8 items
76+
var tuple9 = (new CqlTupleMetadata([], []), "item1", 2, 3, 4, 5, 6, 7, 8, 9);
77+
var tuple10 = (new CqlTupleMetadata([], []), "item1", 2, 3, 4, 5, 6, 7, 8, 9, 10);
78+
var tuple15 = (new CqlTupleMetadata([], []), "item1", 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
79+
80+
// Act
81+
bool result9 = tuple9.GetType().IsCqlValueTuple();
82+
bool result10 = tuple10.GetType().IsCqlValueTuple();
83+
bool result15 = tuple15.GetType().IsCqlValueTuple();
84+
85+
// Assert
86+
Assert.IsTrue(result9, "9-item tuple should be recognized as CqlValueTuple");
87+
Assert.IsTrue(result10, "10-item tuple should be recognized as CqlValueTuple");
88+
Assert.IsTrue(result15, "15-item tuple should be recognized as CqlValueTuple");
89+
}
7190
}

Cql/CoreTests/ToolkitTests.cs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Hl7.Cql.Elm;
1717
using Hl7.Cql.Fhir;
1818
using Hl7.Cql.Invocation.Toolkit.Extensions;
19+
using Hl7.Cql.Primitives;
1920
using Hl7.Cql.Runtime;
2021

2122
namespace CoreTests;
@@ -392,4 +393,210 @@ public void DefinitionInvokerParameters_Tests()
392393
keywordTestInvoker.Parameters[1].Name.Should().Be("ref", "Second parameter should preserve original CQL name even if it's a C# keyword");
393394
keywordTestInvoker.Parameters[2].Name.Should().Be("class", "Third parameter should preserve original CQL name even if it's a C# keyword");
394395
}
396+
397+
[TestMethod]
398+
public void TestLargeTupleParameterInvocation_9Items()
399+
{
400+
// Arrange: Create a CQL library with a function that takes a large tuple parameter (9 items)
401+
var cqlLibraryString = CqlLibraryString.Parse(
402+
"""
403+
library LargeTupleInvocationTest version '1.0.0'
404+
using FHIR version '4.0.1'
405+
406+
context Patient
407+
408+
define function "ProcessLargeTuple"(largeTuple Tuple{
409+
item1 String,
410+
item2 Integer,
411+
item3 Boolean,
412+
item4 Decimal,
413+
item5 String,
414+
item6 Integer,
415+
item7 Boolean,
416+
item8 Decimal,
417+
item9 String
418+
}):
419+
'Processed: ' + largeTuple.item1 + ', ' + largeTuple.item9
420+
""");
421+
422+
var cqlToolkit = new CqlToolkit()
423+
.AddCqlLibraries(cqlLibraryString);
424+
425+
using var librarySetInvoker = cqlToolkit.CreateLibrarySetInvoker();
426+
var ctx = FhirCqlContext.ForBundle();
427+
var libraryInvoker = librarySetInvoker.LibraryInvokers[cqlLibraryString];
428+
429+
// Act: Inspect the function signature that was actually created by the CQL compiler
430+
var processTupleFunction = libraryInvoker.Definitions.Values
431+
.FirstOrDefault(d => d.DefinitionName == "ProcessLargeTuple");
432+
433+
// Assert: Function should be found and have the expected structure
434+
processTupleFunction.Should().NotBeNull("ProcessLargeTuple function should exist");
435+
processTupleFunction!.Parameters.Should().HaveCount(1, "Function should have exactly one parameter");
436+
437+
var parameterType = processTupleFunction.Parameters[0].Type;
438+
parameterType.Should().NotBeNull("Parameter should have a type");
439+
440+
// Let's examine what type the CQL compiler actually generated
441+
var underlyingType = parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(Nullable<>)
442+
? parameterType.GetGenericArguments()[0]
443+
: parameterType;
444+
445+
// Verify this is indeed a large tuple with nested structure
446+
underlyingType.IsCqlValueTuple().Should().BeTrue("The underlying ValueTuple should be recognized as a CQL value tuple");
447+
typeof(ITuple).IsAssignableFrom(underlyingType).Should().BeTrue("The underlying ValueTuple should implement ITuple");
448+
449+
// Create a tuple instance that matches the discovered structure
450+
var metadata = new CqlTupleMetadata([
451+
typeof(string), typeof(int?), typeof(bool?), typeof(decimal?), typeof(string),
452+
typeof(int?), typeof(bool?), typeof(decimal?), typeof(string)
453+
], ["item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9"]);
454+
455+
// Create the large tuple (9 items) that will have the nested structure
456+
var largeTuple9 = (metadata, "first", (int?)42, (bool?)true, (decimal?)3.14m, "middle",
457+
(int?)100, (bool?)false, (decimal?)99.99m, "last");
458+
459+
// Verify the created tuple matches the expected underlying type
460+
largeTuple9.GetType().Should().Be(underlyingType, "Created tuple type should match the underlying function parameter type");
461+
462+
// Act: Invoke the function with the large tuple
463+
var result = processTupleFunction.Invoke(ctx, largeTuple9);
464+
465+
// Assert: Function should execute successfully with large tuple
466+
result.Should().NotBeNull("Function invocation should return a result");
467+
result.Should().Be("Processed: first, last", "Function should process the large tuple correctly");
468+
469+
// Verify that large tuple signature matching works properly for finding definitions
470+
var signature = processTupleFunction.DefinitionSignature;
471+
var foundBySignature = libraryInvoker.Definitions.GetValueOrDefault(signature);
472+
473+
foundBySignature.Should().Be(processTupleFunction, "Should be able to find function by its signature");
474+
foundBySignature!.DefinitionSignature.ParameterTypes.Should().HaveCount(1, "Signature should show one parameter");
475+
foundBySignature.DefinitionSignature.ParameterTypes[0].Should().Be(parameterType, "Signature should include correct parameter type");
476+
}
477+
478+
[TestMethod]
479+
public void TestLargeTupleParameterSignature_15Items()
480+
{
481+
// Arrange: Create a CQL library with a function that takes a very large tuple parameter (15 items)
482+
var cqlLibraryString = CqlLibraryString.Parse(
483+
"""
484+
library VerifyLargeTupleSignatureTest version '1.0.0'
485+
using FHIR version '4.0.1'
486+
487+
context Patient
488+
489+
define function "ProcessVeryLargeTuple"(veryLargeTuple Tuple{
490+
item1 String, item2 Integer, item3 Boolean, item4 Decimal, item5 String,
491+
item6 Integer, item7 Boolean, item8 Decimal, item9 String, item10 Integer,
492+
item11 Boolean, item12 Decimal, item13 String, item14 Integer, item15 Boolean
493+
}): 'Processed 15 items'
494+
""");
495+
496+
var cqlToolkit = new CqlToolkit()
497+
.AddCqlLibraries(cqlLibraryString);
498+
499+
using var librarySetInvoker = cqlToolkit.CreateLibrarySetInvoker();
500+
var libraryInvoker = librarySetInvoker.LibraryInvokers[cqlLibraryString];
501+
502+
// Act: Find the function and inspect its signature
503+
var processFunction = libraryInvoker.Definitions.Values
504+
.FirstOrDefault(d => d.DefinitionName == "ProcessVeryLargeTuple");
505+
506+
// Assert: Function should be found with very large tuple parameter
507+
processFunction.Should().NotBeNull("ProcessVeryLargeTuple function should exist");
508+
processFunction!.Parameters.Should().HaveCount(1, "Function should have exactly one parameter");
509+
510+
var parameterType = processFunction.Parameters[0].Type;
511+
512+
// Get underlying type (removing Nullable wrapper if present)
513+
var underlyingType = parameterType!.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(Nullable<>)
514+
? parameterType.GetGenericArguments()[0]
515+
: parameterType;
516+
517+
// Verify this is a large tuple that uses nested ValueTuple structures
518+
underlyingType.IsCqlValueTuple().Should().BeTrue("15-item tuple parameter should be recognized as a CQL value tuple");
519+
typeof(ITuple).IsAssignableFrom(underlyingType).Should().BeTrue("15-item tuple should implement ITuple");
520+
521+
// Verify the function signature can be used for lookup
522+
var signature = processFunction.DefinitionSignature;
523+
var foundBySignature = libraryInvoker.Definitions.GetValueOrDefault(signature);
524+
525+
foundBySignature.Should().Be(processFunction, "Should be able to find very large tuple function by its signature");
526+
527+
// The fact that we can create a function with 15 tuple items and find it by signature
528+
// demonstrates that large tuple signature discovery works correctly with the invocation toolkit
529+
}
530+
531+
[TestMethod]
532+
public void TestMultipleLargeTupleFunctions_SignatureDistinction()
533+
{
534+
// Arrange: Create CQL library with multiple functions having different large tuple signatures
535+
var cqlLibraryString = CqlLibraryString.Parse(
536+
"""
537+
library MultipleLargeTupleFunctionsTest version '1.0.0'
538+
using FHIR version '4.0.1'
539+
540+
context Patient
541+
542+
define function "ProcessTuple9"(tuple9 Tuple{
543+
a String, b Integer, c Boolean, d Decimal, e String,
544+
f Integer, g Boolean, h Decimal, i String
545+
}): 'Tuple9 processed'
546+
547+
define function "ProcessTuple10"(tuple10 Tuple{
548+
a String, b Integer, c Boolean, d Decimal, e String,
549+
f Integer, g Boolean, h Decimal, i String, j Integer
550+
}): 'Tuple10 processed'
551+
""");
552+
553+
var cqlToolkit = new CqlToolkit()
554+
.AddCqlLibraries(cqlLibraryString);
555+
556+
using var librarySetInvoker = cqlToolkit.CreateLibrarySetInvoker();
557+
var libraryInvoker = librarySetInvoker.LibraryInvokers[cqlLibraryString];
558+
559+
// Act: Find both functions and verify they have different signatures
560+
var tuple9Function = libraryInvoker.Definitions.Values
561+
.FirstOrDefault(d => d.DefinitionName == "ProcessTuple9");
562+
var tuple10Function = libraryInvoker.Definitions.Values
563+
.FirstOrDefault(d => d.DefinitionName == "ProcessTuple10");
564+
565+
// Assert: Both functions should be found with distinct signatures
566+
tuple9Function.Should().NotBeNull("ProcessTuple9 should be found");
567+
tuple10Function.Should().NotBeNull("ProcessTuple10 should be found");
568+
569+
tuple9Function!.Parameters.Should().HaveCount(1, "ProcessTuple9 should have 1 parameter");
570+
tuple10Function!.Parameters.Should().HaveCount(1, "ProcessTuple10 should have 1 parameter");
571+
572+
// Verify they have different parameter types (9 vs 10 items)
573+
var param9Type = tuple9Function.Parameters[0].Type;
574+
var param10Type = tuple10Function.Parameters[0].Type;
575+
576+
param9Type.Should().NotBe(param10Type, "9-item and 10-item tuple functions should have different parameter types");
577+
578+
// Both underlying types should be recognized as CQL value tuples
579+
var underlying9 = param9Type!.IsGenericType && param9Type.GetGenericTypeDefinition() == typeof(Nullable<>)
580+
? param9Type.GetGenericArguments()[0] : param9Type;
581+
var underlying10 = param10Type!.IsGenericType && param10Type.GetGenericTypeDefinition() == typeof(Nullable<>)
582+
? param10Type.GetGenericArguments()[0] : param10Type;
583+
584+
underlying9.IsCqlValueTuple().Should().BeTrue("9-item tuple parameter should be recognized as CQL value tuple");
585+
underlying10.IsCqlValueTuple().Should().BeTrue("10-item tuple parameter should be recognized as CQL value tuple");
586+
587+
// Verify signature distinction works by looking up functions by their complete signatures
588+
var signature9 = tuple9Function.DefinitionSignature;
589+
var signature10 = tuple10Function.DefinitionSignature;
590+
591+
signature9.Should().NotBe(signature10, "Functions should have distinct signatures");
592+
593+
var foundBySignature9 = libraryInvoker.Definitions.GetValueOrDefault(signature9);
594+
var foundBySignature10 = libraryInvoker.Definitions.GetValueOrDefault(signature10);
595+
596+
foundBySignature9.Should().Be(tuple9Function, "Should find ProcessTuple9 by its signature");
597+
foundBySignature10.Should().Be(tuple10Function, "Should find ProcessTuple10 by its signature");
598+
599+
// This demonstrates that the invocation toolkit can distinguish between
600+
// different large tuple signatures and find the correct definitions
601+
}
395602
}

0 commit comments

Comments
 (0)