@@ -990,6 +990,16 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
990990 }
991991
992992 if config .HasOperation ("ttl" ) {
993+ t .Run ("set ttl with bad value should error" , func (t * testing.T ) {
994+ require .Error (t , statestore .Set (context .Background (), & state.SetRequest {
995+ Key : key + "-ttl" ,
996+ Value : "⏱️" ,
997+ Metadata : map [string ]string {
998+ "ttlInSeconds" : "foo" ,
999+ },
1000+ }))
1001+ })
1002+
9931003 t .Run ("set and get with TTL" , func (t * testing.T ) {
9941004 // Check if ttl feature is listed
9951005 features := statestore .Features ()
@@ -1031,6 +1041,183 @@ func ConformanceTests(t *testing.T, props map[string]string, statestore state.St
10311041 features := statestore .Features ()
10321042 require .False (t , state .FeatureTTL .IsPresent (features ))
10331043 })
1044+
1045+ t .Run ("no TTL should not return any expire time" , func (t * testing.T ) {
1046+ err := statestore .Set (context .Background (), & state.SetRequest {
1047+ Key : key + "-no-ttl" ,
1048+ Value : "⏱️" ,
1049+ Metadata : map [string ]string {},
1050+ })
1051+ require .NoError (t , err )
1052+
1053+ // Request immediately
1054+ res , err := statestore .Get (context .Background (), & state.GetRequest {Key : key + "-no-ttl" })
1055+ require .NoError (t , err )
1056+ assertEquals (t , "⏱️" , res )
1057+
1058+ assert .NotContains (t , res .Metadata , "ttlExpireTime" )
1059+ })
1060+
1061+ t .Run ("ttlExpireTime" , func (t * testing.T ) {
1062+ if ! config .HasOperation ("transaction" ) {
1063+ // This test is only for state stores that support transactions
1064+ return
1065+ }
1066+
1067+ unsupported := []string {
1068+ "redis.v6" ,
1069+ "redis.v7" ,
1070+ "etcd.v1" ,
1071+ }
1072+
1073+ for _ , noSup := range unsupported {
1074+ if strings .Contains (config .ComponentName , noSup ) {
1075+ t .Skipf ("skipping test for unsupported state store %s" , noSup )
1076+ }
1077+ }
1078+
1079+ t .Run ("set and get expire time" , func (t * testing.T ) {
1080+ now := time .Now ()
1081+ err := statestore .Set (context .Background (), & state.SetRequest {
1082+ Key : key + "-ttl-expire-time" ,
1083+ Value : "⏱️" ,
1084+ Metadata : map [string ]string {
1085+ // Expire in an hour.
1086+ "ttlInSeconds" : "3600" ,
1087+ },
1088+ })
1089+ require .NoError (t , err )
1090+
1091+ // Request immediately
1092+ res , err := statestore .Get (context .Background (), & state.GetRequest {
1093+ Key : key + "-ttl-expire-time" ,
1094+ })
1095+ require .NoError (t , err )
1096+ assertEquals (t , "⏱️" , res )
1097+
1098+ require .Containsf (t , res .Metadata , "ttlExpireTime" , "expected metadata to contain ttlExpireTime" )
1099+ expireTime , err := time .Parse (time .RFC3339 , res .Metadata ["ttlExpireTime" ])
1100+ require .NoError (t , err )
1101+ assert .InDelta (t , now .Add (time .Hour ).UnixMilli (), expireTime .UnixMilli (), float64 (time .Minute * 10 ))
1102+ })
1103+
1104+ t .Run ("ttl set to -1 should remove the TTL of a state store key" , func (t * testing.T ) {
1105+ req := func (meta map [string ]string ) * state.SetRequest {
1106+ return & state.SetRequest {
1107+ Key : key + "-ttl-expire-time-minus-1" ,
1108+ Value : "⏱️" ,
1109+ Metadata : meta ,
1110+ }
1111+ }
1112+
1113+ require .NoError (t , statestore .Set (context .Background (), req (map [string ]string {
1114+ // Expire in 2 seconds.
1115+ "ttlInSeconds" : "2" ,
1116+ })))
1117+
1118+ // Request immediately
1119+ res , err := statestore .Get (context .Background (), & state.GetRequest {
1120+ Key : key + "-ttl-expire-time-minus-1" ,
1121+ })
1122+ require .NoError (t , err )
1123+ assertEquals (t , "⏱️" , res )
1124+ assert .Contains (t , res .Metadata , "ttlExpireTime" )
1125+
1126+ // Remove TTL by setting a value of -1.
1127+ require .NoError (t , statestore .Set (context .Background (), req (map [string ]string {
1128+ "ttlInSeconds" : "-1" ,
1129+ })))
1130+ res , err = statestore .Get (context .Background (), & state.GetRequest {
1131+ Key : key + "-ttl-expire-time-minus-1" ,
1132+ })
1133+ require .NoError (t , err )
1134+ assertEquals (t , "⏱️" , res )
1135+ assert .NotContains (t , res .Metadata , "ttlExpireTime" )
1136+
1137+ // Ensure that the key is not expired after previous TTL.
1138+ time .Sleep (3 * time .Second )
1139+
1140+ res , err = statestore .Get (context .Background (), & state.GetRequest {
1141+ Key : key + "-ttl-expire-time-minus-1" ,
1142+ })
1143+ require .NoError (t , err )
1144+ assertEquals (t , "⏱️" , res )
1145+
1146+ // Set a new TTL.
1147+ require .NoError (t , statestore .Set (context .Background (), req (map [string ]string {
1148+ "ttlInSeconds" : "2" ,
1149+ })))
1150+ res , err = statestore .Get (context .Background (), & state.GetRequest {
1151+ Key : key + "-ttl-expire-time-minus-1" ,
1152+ })
1153+ require .NoError (t , err )
1154+ assertEquals (t , "⏱️" , res )
1155+ assert .Contains (t , res .Metadata , "ttlExpireTime" )
1156+
1157+ // Remove TTL by omitting the ttlInSeconds field.
1158+ require .NoError (t , statestore .Set (context .Background (), req (map [string ]string {})))
1159+ res , err = statestore .Get (context .Background (), & state.GetRequest {
1160+ Key : key + "-ttl-expire-time-minus-1" ,
1161+ })
1162+ require .NoError (t , err )
1163+ assertEquals (t , "⏱️" , res )
1164+ assert .NotContains (t , res .Metadata , "ttlExpireTime" )
1165+
1166+ // Ensure key is not expired after previous TTL.
1167+ time .Sleep (3 * time .Second )
1168+ res , err = statestore .Get (context .Background (), & state.GetRequest {
1169+ Key : key + "-ttl-expire-time-minus-1" ,
1170+ })
1171+ require .NoError (t , err )
1172+ assertEquals (t , "⏱️" , res )
1173+ assert .NotContains (t , res .Metadata , "ttlExpireTime" )
1174+ })
1175+
1176+ t .Run ("set and get expire time bulkGet" , func (t * testing.T ) {
1177+ now := time .Now ()
1178+ require .NoError (t , statestore .Set (context .Background (), & state.SetRequest {
1179+ Key : key + "-ttl-expire-time-bulk-1" ,
1180+ Value : "123" ,
1181+ Metadata : map [string ]string {"ttlInSeconds" : "3600" },
1182+ }))
1183+
1184+ require .NoError (t , statestore .Set (context .Background (), & state.SetRequest {
1185+ Key : key + "-ttl-expire-time-bulk-2" ,
1186+ Value : "234" ,
1187+ Metadata : map [string ]string {"ttlInSeconds" : "3600" },
1188+ }))
1189+
1190+ // Request immediately
1191+ res , err := statestore .BulkGet (context .Background (), []state.GetRequest {
1192+ {Key : key + "-ttl-expire-time-bulk-1" },
1193+ {Key : key + "-ttl-expire-time-bulk-2" },
1194+ }, state.BulkGetOpts {})
1195+ require .NoError (t , err )
1196+
1197+ require .Len (t , res , 2 )
1198+ sort .Slice (res , func (i , j int ) bool {
1199+ return res [i ].Key < res [j ].Key
1200+ })
1201+
1202+ assert .Equal (t , key + "-ttl-expire-time-bulk-1" , res [0 ].Key )
1203+ assert .Equal (t , key + "-ttl-expire-time-bulk-2" , res [1 ].Key )
1204+ assert .Equal (t , []byte (`"123"` ), res [0 ].Data )
1205+ assert .Equal (t , []byte (`"234"` ), res [1 ].Data )
1206+
1207+ for i := range res {
1208+ if config .HasOperation ("transaction" ) {
1209+ require .Containsf (t , res [i ].Metadata , "ttlExpireTime" , "expected metadata to contain ttlExpireTime" )
1210+ expireTime , err := time .Parse (time .RFC3339 , res [i ].Metadata ["ttlExpireTime" ])
1211+ require .NoError (t , err )
1212+ // Check the expire time is returned and is in a 10 minute window. This
1213+ // window should be _more_ than enough.
1214+ assert .InDelta (t , now .Add (time .Hour ).UnixMilli (), expireTime .UnixMilli (), float64 (time .Minute * 10 ))
1215+ } else {
1216+ assert .NotContains (t , res [i ].Metadata , "ttlExpireTime" )
1217+ }
1218+ }
1219+ })
1220+ })
10341221 }
10351222}
10361223
0 commit comments