@@ -612,6 +612,170 @@ class ModelGeneratorPolymorphicTest {
612612 )
613613 }
614614
615+ // -- CEM-01: boolean discriminator names (KotlinPoet handles escaping) --
616+
617+ @Test
618+ fun `boolean discriminator names produce valid data classes` () {
619+ val deviceStatusSchema = schema(
620+ name = " DeviceStatus" ,
621+ oneOf = listOf (
622+ TypeRef .Reference (" true" ),
623+ TypeRef .Reference (" false" ),
624+ ),
625+ discriminator = Discriminator (
626+ propertyName = " online" ,
627+ mapping = mapOf (
628+ " true" to " #/components/schemas/true" ,
629+ " false" to " #/components/schemas/false" ,
630+ ),
631+ ),
632+ )
633+ val trueSchema = schema(
634+ name = " true" ,
635+ properties = listOf (
636+ PropertyModel (" connectedSince" , TypeRef .Primitive (PrimitiveType .STRING ), null , false ),
637+ ),
638+ requiredProperties = setOf (" connectedSince" ),
639+ )
640+ val falseSchema = schema(
641+ name = " false" ,
642+ properties = listOf (
643+ PropertyModel (" lastSeen" , TypeRef .Primitive (PrimitiveType .STRING ), null , false ),
644+ ),
645+ requiredProperties = setOf (" lastSeen" ),
646+ )
647+
648+ val files = generator.generate(
649+ spec(schemas = listOf (deviceStatusSchema, trueSchema, falseSchema)),
650+ )
651+
652+ val trueType = findType(files, " true" )
653+ assertTrue(KModifier .DATA in trueType.modifiers, " 'true' should be data class" )
654+
655+ val falseType = findType(files, " false" )
656+ assertTrue(KModifier .DATA in falseType.modifiers, " 'false' should be data class" )
657+
658+ // Both implement DeviceStatus sealed interface
659+ val trueSuperinterfaces = trueType.superinterfaces.keys.map { it.toString() }
660+ assertTrue(
661+ " $modelPackage .DeviceStatus" in trueSuperinterfaces,
662+ " 'true' should implement DeviceStatus. Superinterfaces: $trueSuperinterfaces " ,
663+ )
664+ val falseSuperinterfaces = falseType.superinterfaces.keys.map { it.toString() }
665+ assertTrue(
666+ " $modelPackage .DeviceStatus" in falseSuperinterfaces,
667+ " 'false' should implement DeviceStatus. Superinterfaces: $falseSuperinterfaces " ,
668+ )
669+ }
670+
671+ @Test
672+ fun `all oneOf variant schemas generate data classes even with many subtypes` () {
673+ val variantNames = listOf (
674+ " ExtenderDevice" ,
675+ " EthernetDevice" ,
676+ " WanDevice" ,
677+ " USBDevice" ,
678+ " WiFiDevice" ,
679+ " OtherDevice" ,
680+ )
681+
682+ val networkMeshSchema = schema(
683+ name = " NetworkMeshDevice" ,
684+ oneOf = variantNames.map { TypeRef .Reference (it) },
685+ discriminator = Discriminator (
686+ propertyName = " deviceType" ,
687+ mapping = variantNames.associateWith { " #/components/schemas/$it " },
688+ ),
689+ )
690+
691+ val variantSchemas = variantNames.map { name ->
692+ schema(
693+ name = name,
694+ properties = listOf (
695+ PropertyModel (" deviceId" , TypeRef .Primitive (PrimitiveType .STRING ), null , false ),
696+ ),
697+ requiredProperties = setOf (" deviceId" ),
698+ )
699+ }
700+
701+ val files = generator.generate(
702+ spec(schemas = listOf (networkMeshSchema) + variantSchemas),
703+ )
704+
705+ // All 6 variants generated
706+ for (name in variantNames) {
707+ val variantType = findType(files, name)
708+ assertTrue(
709+ KModifier .DATA in variantType.modifiers,
710+ " $name should be a data class" ,
711+ )
712+ val superinterfaces = variantType.superinterfaces.keys.map { it.toString() }
713+ assertTrue(
714+ " $modelPackage .NetworkMeshDevice" in superinterfaces,
715+ " $name should implement NetworkMeshDevice. Superinterfaces: $superinterfaces " ,
716+ )
717+ }
718+
719+ // SerializersModule contains all variants
720+ val serializersModuleFile = files.find { it.name == " SerializersModule" }
721+ assertNotNull(serializersModuleFile, " SerializersModule file should be generated" )
722+ val moduleCode = serializersModuleFile.toString()
723+ for (name in variantNames) {
724+ assertTrue(
725+ name in moduleCode,
726+ " SerializersModule should reference $name . Code: $moduleCode " ,
727+ )
728+ }
729+ }
730+
731+ @Test
732+ fun `SerializersModule includes boolean variant names` () {
733+ val deviceStatusSchema = schema(
734+ name = " DeviceStatus" ,
735+ oneOf = listOf (
736+ TypeRef .Reference (" true" ),
737+ TypeRef .Reference (" false" ),
738+ ),
739+ discriminator = Discriminator (
740+ propertyName = " online" ,
741+ mapping = mapOf (
742+ " true" to " #/components/schemas/true" ,
743+ " false" to " #/components/schemas/false" ,
744+ ),
745+ ),
746+ )
747+ val trueSchema = schema(
748+ name = " true" ,
749+ properties = listOf (
750+ PropertyModel (" connectedSince" , TypeRef .Primitive (PrimitiveType .STRING ), null , false ),
751+ ),
752+ requiredProperties = setOf (" connectedSince" ),
753+ )
754+ val falseSchema = schema(
755+ name = " false" ,
756+ properties = listOf (
757+ PropertyModel (" lastSeen" , TypeRef .Primitive (PrimitiveType .STRING ), null , false ),
758+ ),
759+ requiredProperties = setOf (" lastSeen" ),
760+ )
761+
762+ val files = generator.generate(
763+ spec(schemas = listOf (deviceStatusSchema, trueSchema, falseSchema)),
764+ )
765+
766+ val serializersModuleFile = files.find { it.name == " SerializersModule" }
767+ assertNotNull(serializersModuleFile, " SerializersModule file should be generated" )
768+ val moduleCode = serializersModuleFile.toString()
769+ assertTrue(
770+ " `true`" in moduleCode,
771+ " SerializersModule should reference `true`. Code: $moduleCode " ,
772+ )
773+ assertTrue(
774+ " `false`" in moduleCode,
775+ " SerializersModule should reference `false`. Code: $moduleCode " ,
776+ )
777+ }
778+
615779 // -- POLY-06: allOf with sealed parent --
616780
617781 @Test
0 commit comments