@@ -636,3 +636,213 @@ describe('SchemaFactory - makeEnumSchema', () => {
636636 expect ( ( ) => factory . makeEnumSchema ( 'Unknown' as any ) ) . toThrow ( ) ;
637637 } ) ;
638638} ) ;
639+
640+ // --- Computed fields tests ---
641+
642+ const validProduct = {
643+ id : 'prod1' ,
644+ name : 'Widget' ,
645+ price : 10.0 ,
646+ discount : 2.0 ,
647+ finalPrice : 8.0 ,
648+ } ;
649+
650+ describe ( 'SchemaFactory - computed fields' , ( ) => {
651+ describe ( 'makeModelSchema includes computed fields' , ( ) => {
652+ it ( 'accepts a Product with computed field present' , ( ) => {
653+ const productSchema = factory . makeModelSchema ( 'Product' ) ;
654+ expect ( productSchema . safeParse ( validProduct ) . success ) . toBe ( true ) ;
655+ } ) ;
656+
657+ it ( 'rejects a Product missing the computed field' , ( ) => {
658+ const productSchema = factory . makeModelSchema ( 'Product' ) ;
659+ const { finalPrice : _ , ...withoutComputed } = validProduct ;
660+ expect ( productSchema . safeParse ( withoutComputed ) . success ) . toBe ( false ) ;
661+ } ) ;
662+
663+ it ( 'infers computed field in model schema type' , ( ) => {
664+ const _schema = factory . makeModelSchema ( 'Product' ) ;
665+ type Product = z . infer < typeof _schema > ;
666+ expectTypeOf < Product [ 'finalPrice' ] > ( ) . toEqualTypeOf < number > ( ) ;
667+ } ) ;
668+ } ) ;
669+
670+ describe ( 'makeModelCreateSchema excludes computed fields' , ( ) => {
671+ it ( 'accepts a Product without the computed field' , ( ) => {
672+ const createSchema = factory . makeModelCreateSchema ( 'Product' ) ;
673+ expect ( createSchema . safeParse ( { name : 'Widget' , price : 10.0 } ) . success ) . toBe ( true ) ;
674+ } ) ;
675+
676+ it ( 'rejects a Product with the computed field (strict)' , ( ) => {
677+ const createSchema = factory . makeModelCreateSchema ( 'Product' ) ;
678+ expect ( createSchema . safeParse ( { name : 'Widget' , price : 10.0 , finalPrice : 8.0 } ) . success ) . toBe ( false ) ;
679+ } ) ;
680+
681+ it ( 'does not include computed field in create schema type' , ( ) => {
682+ const _schema = factory . makeModelCreateSchema ( 'Product' ) ;
683+ type ProductCreate = z . infer < typeof _schema > ;
684+ expectTypeOf < ProductCreate > ( ) . not . toHaveProperty ( 'finalPrice' ) ;
685+ // own fields are present
686+ expectTypeOf < ProductCreate > ( ) . toHaveProperty ( 'name' ) ;
687+ expectTypeOf < ProductCreate [ 'name' ] > ( ) . toEqualTypeOf < string > ( ) ;
688+ expectTypeOf < ProductCreate [ 'price' ] > ( ) . toEqualTypeOf < number > ( ) ;
689+ // field with default is optional
690+ expectTypeOf < ProductCreate > ( ) . toHaveProperty ( 'discount' ) ;
691+ } ) ;
692+ } ) ;
693+
694+ describe ( 'makeModelUpdateSchema excludes computed fields' , ( ) => {
695+ it ( 'accepts a Product update without the computed field' , ( ) => {
696+ const updateSchema = factory . makeModelUpdateSchema ( 'Product' ) ;
697+ expect ( updateSchema . safeParse ( { price : 12.0 } ) . success ) . toBe ( true ) ;
698+ } ) ;
699+
700+ it ( 'rejects a Product update with the computed field (strict)' , ( ) => {
701+ const updateSchema = factory . makeModelUpdateSchema ( 'Product' ) ;
702+ expect ( updateSchema . safeParse ( { price : 12.0 , finalPrice : 10.0 } ) . success ) . toBe ( false ) ;
703+ } ) ;
704+
705+ it ( 'does not include computed field in update schema type' , ( ) => {
706+ const _schema = factory . makeModelUpdateSchema ( 'Product' ) ;
707+ type ProductUpdate = z . infer < typeof _schema > ;
708+ expectTypeOf < ProductUpdate > ( ) . not . toHaveProperty ( 'finalPrice' ) ;
709+ // own fields are present (all optional in update)
710+ expectTypeOf < ProductUpdate > ( ) . toHaveProperty ( 'name' ) ;
711+ } ) ;
712+ } ) ;
713+ } ) ;
714+
715+ // --- Delegate model tests ---
716+
717+ const validVideo = {
718+ id : 1 ,
719+ createdAt : new Date ( ) ,
720+ assetType : 'Video' ,
721+ duration : 120 ,
722+ url : 'https://example.com/video.mp4' ,
723+ } ;
724+
725+ const validImage = {
726+ id : 2 ,
727+ createdAt : new Date ( ) ,
728+ assetType : 'Image' ,
729+ format : 'png' ,
730+ width : 800 ,
731+ } ;
732+
733+ describe ( 'SchemaFactory - delegate models' , ( ) => {
734+ describe ( 'makeModelSchema for delegate base model' , ( ) => {
735+ it ( 'accepts a valid Asset' , ( ) => {
736+ const assetSchema = factory . makeModelSchema ( 'Asset' ) ;
737+ expect ( assetSchema . safeParse ( { id : 1 , createdAt : new Date ( ) , assetType : 'Video' } ) . success ) . toBe ( true ) ;
738+ } ) ;
739+
740+ it ( 'includes discriminator field in model schema type' , ( ) => {
741+ const _schema = factory . makeModelSchema ( 'Asset' ) ;
742+ type Asset = z . infer < typeof _schema > ;
743+ expectTypeOf < Asset [ 'assetType' ] > ( ) . toEqualTypeOf < string > ( ) ;
744+ expectTypeOf < Asset [ 'id' ] > ( ) . toEqualTypeOf < number > ( ) ;
745+ } ) ;
746+ } ) ;
747+
748+ describe ( 'makeModelSchema for derived models' , ( ) => {
749+ it ( 'accepts a valid Video (includes inherited + own fields)' , ( ) => {
750+ const videoSchema = factory . makeModelSchema ( 'Video' ) ;
751+ expect ( videoSchema . safeParse ( validVideo ) . success ) . toBe ( true ) ;
752+ } ) ;
753+
754+ it ( 'accepts a valid Image (includes inherited + own fields)' , ( ) => {
755+ const imageSchema = factory . makeModelSchema ( 'Image' ) ;
756+ expect ( imageSchema . safeParse ( validImage ) . success ) . toBe ( true ) ;
757+ } ) ;
758+
759+ it ( 'rejects Video missing own fields' , ( ) => {
760+ const videoSchema = factory . makeModelSchema ( 'Video' ) ;
761+ const { duration : _ , url : _u , ...withoutOwn } = validVideo ;
762+ expect ( videoSchema . safeParse ( withoutOwn ) . success ) . toBe ( false ) ;
763+ } ) ;
764+
765+ it ( 'infers correct types for derived model including inherited fields' , ( ) => {
766+ const _schema = factory . makeModelSchema ( 'Video' ) ;
767+ type Video = z . infer < typeof _schema > ;
768+ // inherited fields
769+ expectTypeOf < Video [ 'id' ] > ( ) . toEqualTypeOf < number > ( ) ;
770+ expectTypeOf < Video [ 'assetType' ] > ( ) . toEqualTypeOf < string > ( ) ;
771+ // own fields
772+ expectTypeOf < Video [ 'duration' ] > ( ) . toEqualTypeOf < number > ( ) ;
773+ expectTypeOf < Video [ 'url' ] > ( ) . toEqualTypeOf < string > ( ) ;
774+ } ) ;
775+ } ) ;
776+
777+ describe ( 'makeModelCreateSchema excludes discriminator' , ( ) => {
778+ it ( 'accepts Video create without discriminator and inherited fields' , ( ) => {
779+ const createSchema = factory . makeModelCreateSchema ( 'Video' ) ;
780+ // Only own non-inherited, non-discriminator fields should be required
781+ expect ( createSchema . safeParse ( { duration : 120 , url : 'https://example.com/video.mp4' } ) . success ) . toBe ( true ) ;
782+ } ) ;
783+
784+ it ( 'rejects Video create with discriminator field (strict)' , ( ) => {
785+ const createSchema = factory . makeModelCreateSchema ( 'Video' ) ;
786+ expect (
787+ createSchema . safeParse ( {
788+ duration : 120 ,
789+ url : 'https://example.com/video.mp4' ,
790+ assetType : 'Video' ,
791+ } ) . success ,
792+ ) . toBe ( false ) ;
793+ } ) ;
794+
795+ it ( 'does not include discriminator fields in create schema type' , ( ) => {
796+ const _schema = factory . makeModelCreateSchema ( 'Video' ) ;
797+ type VideoCreate = z . infer < typeof _schema > ;
798+ // discriminator and originModel fields should be excluded
799+ expectTypeOf < VideoCreate > ( ) . not . toHaveProperty ( 'assetType' ) ;
800+ // own fields should be present
801+ expectTypeOf < VideoCreate > ( ) . toHaveProperty ( 'duration' ) ;
802+ expectTypeOf < VideoCreate > ( ) . toHaveProperty ( 'url' ) ;
803+ expectTypeOf < VideoCreate [ 'duration' ] > ( ) . toEqualTypeOf < number > ( ) ;
804+ expectTypeOf < VideoCreate [ 'url' ] > ( ) . toEqualTypeOf < string > ( ) ;
805+ } ) ;
806+
807+ it ( 'excludes discriminator from base delegate create schema' , ( ) => {
808+ const createSchema = factory . makeModelCreateSchema ( 'Asset' ) ;
809+ // discriminator should not be included
810+ expect ( createSchema . safeParse ( { assetType : 'Video' } ) . success ) . toBe ( false ) ;
811+ // empty create (id has default, createdAt has default, assetType is discriminator)
812+ expect ( createSchema . safeParse ( { } ) . success ) . toBe ( true ) ;
813+ } ) ;
814+
815+ it ( 'does not include discriminator in base delegate create schema type' , ( ) => {
816+ const _schema = factory . makeModelCreateSchema ( 'Asset' ) ;
817+ type AssetCreate = z . infer < typeof _schema > ;
818+ expectTypeOf < AssetCreate > ( ) . not . toHaveProperty ( 'assetType' ) ;
819+ } ) ;
820+ } ) ;
821+
822+ describe ( 'makeModelUpdateSchema excludes discriminator and originModel fields' , ( ) => {
823+ it ( 'accepts Video update with only own fields' , ( ) => {
824+ const updateSchema = factory . makeModelUpdateSchema ( 'Video' ) ;
825+ expect ( updateSchema . safeParse ( { duration : 180 } ) . success ) . toBe ( true ) ;
826+ } ) ;
827+
828+ it ( 'rejects Video update with discriminator field (strict)' , ( ) => {
829+ const updateSchema = factory . makeModelUpdateSchema ( 'Video' ) ;
830+ expect ( updateSchema . safeParse ( { duration : 180 , assetType : 'Video' } ) . success ) . toBe ( false ) ;
831+ } ) ;
832+
833+ it ( 'does not include discriminator fields in update schema type' , ( ) => {
834+ const _schema = factory . makeModelUpdateSchema ( 'Video' ) ;
835+ type VideoUpdate = z . infer < typeof _schema > ;
836+ expectTypeOf < VideoUpdate > ( ) . not . toHaveProperty ( 'assetType' ) ;
837+ // own fields should be present (all optional in update)
838+ expectTypeOf < VideoUpdate > ( ) . toHaveProperty ( 'duration' ) ;
839+ expectTypeOf < VideoUpdate > ( ) . toHaveProperty ( 'url' ) ;
840+ } ) ;
841+
842+ it ( 'does not include discriminator in base delegate update schema type' , ( ) => {
843+ const _schema = factory . makeModelUpdateSchema ( 'Asset' ) ;
844+ type AssetUpdate = z . infer < typeof _schema > ;
845+ expectTypeOf < AssetUpdate > ( ) . not . toHaveProperty ( 'assetType' ) ;
846+ } ) ;
847+ } ) ;
848+ } ) ;
0 commit comments