github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/it/impl_uniques_test.go (about) 1 /* 2 * Copyright (c) 2020-present unTill Pro, Ltd. 3 */ 4 5 package sys_it 6 7 import ( 8 "bytes" 9 "encoding/base64" 10 "encoding/binary" 11 "fmt" 12 "strings" 13 "testing" 14 15 "github.com/stretchr/testify/require" 16 17 "github.com/voedger/voedger/pkg/appdef" 18 "github.com/voedger/voedger/pkg/istructs" 19 "github.com/voedger/voedger/pkg/sys/uniques" 20 coreutils "github.com/voedger/voedger/pkg/utils" 21 it "github.com/voedger/voedger/pkg/vit" 22 ) 23 24 func getUniqueNumber(vit *it.VIT) (int, string) { 25 uniqueNumber := vit.NextNumber() 26 buf := bytes.NewBuffer(nil) 27 require.NoError(vit.T, binary.Write(buf, binary.BigEndian, uint32(uniqueNumber))) 28 uniqueBytes := base64.StdEncoding.EncodeToString(buf.Bytes()) 29 return uniqueNumber, uniqueBytes 30 } 31 32 func TestBasicUsage_Uniques(t *testing.T) { 33 require := require.New(t) 34 vit := it.NewVIT(t, &it.SharedConfig_App1) 35 defer vit.TearDown() 36 37 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 38 num, bts := getUniqueNumber(vit) 39 40 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 41 expectedRecID := vit.PostWS(ws, "c.sys.CUD", body).NewID() 42 43 t.Run("409 on duplicate basic", func(t *testing.T) { 44 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 45 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()).Println() 46 }) 47 48 t.Run("409 on duplicate different fields order", func(t *testing.T) { 49 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Str":"str","Bytes":"%s","Int":%d,"Bool":true}}]}`, bts, num) 50 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()).Println() 51 }) 52 53 t.Run("get record id by unique fields values basic", func(t *testing.T) { 54 as, err := vit.AppStructs(istructs.AppQName_test1_app1) 55 require.NoError(err) 56 btsBytes := bytes.NewBuffer(nil) 57 binary.Write(btsBytes, binary.BigEndian, uint32(num)) 58 t.Run("match", func(t *testing.T) { 59 recordID, err := uniques.GetRecordIDByUniqueCombination(ws.WSID, it.QNameApp1_DocConstraints, as, map[string]interface{}{ 60 "Str": "str", 61 "Bytes": btsBytes.Bytes(), 62 "Int": int32(num), 63 "Bool": true, 64 }) 65 require.NoError(err) 66 require.Equal(istructs.RecordID(expectedRecID), recordID) 67 }) 68 t.Run("not found", func(t *testing.T) { 69 recordID, err := uniques.GetRecordIDByUniqueCombination(ws.WSID, it.QNameApp1_DocConstraints, as, map[string]interface{}{ 70 "Str": "str", 71 "Bytes": btsBytes.Bytes(), 72 "Int": int32(num), 73 "Bool": false, // <-- wrong here 74 }) 75 require.NoError(err) 76 require.Zero(recordID) 77 }) 78 }) 79 } 80 81 func TestActivateDeactivateRecordWithUniques(t *testing.T) { 82 vit := it.NewVIT(t, &it.SharedConfig_App1) 83 defer vit.TearDown() 84 85 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 86 num, bts := getUniqueNumber(vit) 87 88 // insert a unique 89 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 90 newID := vit.PostWS(ws, "c.sys.CUD", body).NewID() 91 92 // allowed to deactivate 93 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.IsActive":false}}]}`, newID) 94 vit.PostWS(ws, "c.sys.CUD", body) 95 96 // ok to deactivate again an already inactive record 97 vit.PostWS(ws, "c.sys.CUD", body) 98 99 // still not able to update unique fields even if the record is deactivated 100 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"Int":42}}]}`, newID) 101 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect403()) 102 103 // allowed to activate 104 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.IsActive":true}}]}`, newID) 105 vit.PostWS(ws, "c.sys.CUD", body) 106 107 // ok to activate again the already active record 108 vit.PostWS(ws, "c.sys.CUD", body) 109 110 // check uniques works after deactivate/activate 111 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 112 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()) 113 } 114 115 func TestUniquesUpdate(t *testing.T) { 116 vit := it.NewVIT(t, &it.SharedConfig_App1) 117 defer vit.TearDown() 118 119 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 120 num, bts := getUniqueNumber(vit) 121 122 // insert a unique 123 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 124 prevID := vit.PostWS(ws, "c.sys.CUD", body).NewID() 125 126 // to update unique fields let's deactivate existing record and create new record with new values 127 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.IsActive":false}}]}`, prevID) 128 vit.PostWS(ws, "c.sys.CUD", body) 129 130 // insert a record with new values, i.e. do actually update 131 num, bts = getUniqueNumber(vit) 132 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 133 prevID = vit.PostWS(ws, "c.sys.CUD", body).NewID() 134 135 // let's deactivate the new record 136 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.IsActive":false}}]}`, prevID) 137 vit.PostWS(ws, "c.sys.CUD", body) 138 139 // we're able to insert a new record that conflicts with the deactivated one 140 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 141 vit.PostWS(ws, "c.sys.CUD", body) 142 143 // insert the same again -> unique constraint violation with the new record 144 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()) 145 146 // try to activate previously deactivated record -> conflict with the just inserted new record 147 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.IsActive":true}}]}`, prevID) 148 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()) 149 } 150 151 func TestUniquesDenyUpdate(t *testing.T) { 152 vit := it.NewVIT(t, &it.SharedConfig_App1) 153 defer vit.TearDown() 154 155 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 156 num, bts := getUniqueNumber(vit) 157 158 // insert one unique 159 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 160 newID := vit.PostWS(ws, "c.sys.CUD", body).NewID() 161 162 // deny to modify any unique field 163 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","Int": 1}}]}`, newID) 164 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect403()) 165 } 166 167 func TestFewUniquesOneDoc(t *testing.T) { 168 vit := it.NewVIT(t, &it.SharedConfig_App1) 169 defer vit.TearDown() 170 171 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 172 173 t.Run("same sets of values for 2 different uniques in one doc -> no coflict", func(t *testing.T) { 174 newNum, newBts := getUniqueNumber(vit) 175 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraintsFewUniques", 176 "Int1":%[1]d,"Str1":"str","Bool1":true,"Bytes1":"%[2]s", 177 "Int2":%[1]d,"Str2":"str","Bool2":true,"Bytes2":"%[2]s"}}]}`, newNum, newBts) 178 vit.PostWS(ws, "c.sys.CUD", body) 179 }) 180 181 t.Run("no values for unique fields -> default values are used", func(t *testing.T) { 182 t.Run("uniq1", func(t *testing.T) { 183 184 // insert a record with no values for unique fields -> default values are used as unique values 185 // have values for uniq2, no values for uniq1 186 newNum, _ := getUniqueNumber(vit) 187 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraintsFewUniques", 188 "Int2":%d,"Str2":"str","Bool2":true}}]}`, newNum) 189 vit.PostWS(ws, "c.sys.CUD", body) 190 191 // no values again -> conflict because unique combination for default values exists already 192 // have values for uniq2, no values for uniq1 193 newNum, _ = getUniqueNumber(vit) 194 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraintsFewUniques", 195 "Int2":%d,"Str2":"str","Bool2":true}}]}`, newNum) 196 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409(fmt.Sprintf(`"%s" unique constraint violation`, appdef.UniqueQName(it.QNameApp1_DocConstraintsFewUniques, "01")))).Println() 197 }) 198 t.Run("uniq2", func(t *testing.T) { 199 200 // insert a record with no values for unique fields -> default values are used as unique values 201 // have values for uniq1, no values for uniq2 202 newNum, _ := getUniqueNumber(vit) 203 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraintsFewUniques", 204 "Int1":%d,"Str1":"str","Bool1":true}}]}`, newNum) 205 vit.PostWS(ws, "c.sys.CUD", body) 206 207 // no values again -> conflict because unique combination for default values exists already 208 // have values for uniq1, no values for uniq2 209 newNum, _ = getUniqueNumber(vit) 210 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraintsFewUniques", 211 "Int1":%d,"Str1":"str","Bool1":true}}]}`, newNum) 212 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409(fmt.Sprintf(`"%s" unique constraint violation`, appdef.UniqueQName(it.QNameApp1_DocConstraintsFewUniques, "uniq1")))).Println() 213 }) 214 }) 215 } 216 217 func TestMultipleCUDs(t *testing.T) { 218 vit := it.NewVIT(t, &it.SharedConfig_App1) 219 defer vit.TearDown() 220 221 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 222 223 t.Run("duplicate in cuds", func(t *testing.T) { 224 newNum, newBts := getUniqueNumber(vit) 225 body := fmt.Sprintf(`{"cuds":[ 226 {"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}, 227 {"fields":{"sys.ID":2,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}} 228 ]}`, newNum, newBts, newNum, newBts) 229 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()) 230 }) 231 232 t.Run("update few records in one request", func(t *testing.T) { 233 t.Run("update the inactive record", func(t *testing.T) { 234 num, bts := getUniqueNumber(vit) 235 // insert the first record 236 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 237 id := vit.PostWS(ws, "c.sys.CUD", body).NewID() 238 239 t.Run("any CUD itself produces the conflict -> 409 even if effectively no conflict", func(t *testing.T) { 240 t.Run("produce the conflict by insert, remove the conflict by deactivate existing", func(t *testing.T) { 241 242 // 1st cud should produce the conflict but the 2nd should deactivate the existing record, i.e. effectively there should not be the conflict 243 // but the batch is not atomic so it is possible connection with the storage between 1st and 2nd CUDs that will produce the unique violation in the storage 244 // so that should be denied 245 body = fmt.Sprintf(`{"cuds":[ 246 {"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}, 247 {"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":false}} 248 ]}`, num, bts, id) 249 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()) 250 }) 251 252 t.Run("insert non-conflicting, produce the conflict by activating the existing inactive record", func(t *testing.T) { 253 num, bts := getUniqueNumber(vit) 254 255 // insert one record 256 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 257 id := vit.PostWS(ws, "c.sys.CUD", body).NewID() 258 259 // deactivate it 260 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":false}}]}`, id) 261 vit.PostWS(ws, "c.sys.CUD", body) 262 263 // insert new same (ok) and activate the inactive one -> deny 264 body = fmt.Sprintf(`{"cuds":[ 265 {"fields":{"sys.ID":2,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}, 266 {"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":true}} 267 ]}`, num, bts, id) 268 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()) 269 }) 270 }) 271 272 t.Run("effectively no changes and insert a conflicting record", func(t *testing.T) { 273 body = fmt.Sprintf(`{"cuds":[ 274 {"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":false}}, 275 {"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":true}}, 276 {"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}} 277 ]}`, id, id, num, bts) 278 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()) 279 }) 280 }) 281 t.Run("update the inactive record", func(t *testing.T) { 282 num, bts := getUniqueNumber(vit) 283 284 // insert one record 285 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 286 id1 := vit.PostWS(ws, "c.sys.CUD", body).NewID() 287 288 // deactivate it 289 body = fmt.Sprintf(`{"cuds":[{"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":false}}]}`, id1) 290 vit.PostWS(ws, "c.sys.CUD", body) 291 292 // insert one more the same record 293 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 294 vit.PostWS(ws, "c.sys.CUD", body) 295 296 t.Run("ok to make first conflict by update then fix it immediately because engine stores updates in a map by ID, result is false in our case", func(t *testing.T) { 297 // cmd.reb.cud.updates is map, so first we set one ID isActive=true that shout make a conflict 298 // but on creating the update for the second CUD we overwrite the map by the same ID and write SetActive=false 299 // i.e. update the same record twice -> one update operation with merged fields, result is deactivation 300 // see commandprocessor.writeCUDs() 301 body = fmt.Sprintf(`{"cuds":[ 302 {"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":true}}, 303 {"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":false}} 304 ]}`, id1, id1) 305 vit.PostWS(ws, "c.sys.CUD", body) 306 }) 307 308 t.Run("conflict if effectively activating the conflicting record", func(t *testing.T) { 309 // update the same record twice -> one update operation with merged fields, result is activation 310 body = fmt.Sprintf(`{"cuds":[ 311 {"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":false}}, 312 {"sys.ID":%d,"fields":{"sys.QName":"app1pkg.DocConstraints","sys.IsActive":true}} 313 ]}`, id1, id1) 314 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409()) 315 }) 316 }) 317 }) 318 } 319 320 func TestMaxUniqueLen(t *testing.T) { 321 vit := it.NewVIT(t, &it.SharedConfig_App1) 322 defer vit.TearDown() 323 324 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 325 const bigLen = 42000 326 327 num, _ := getUniqueNumber(vit) 328 longString := strings.Repeat(" ", bigLen) 329 longBytes := make([]byte, bigLen) 330 longBytesBase64 := base64.StdEncoding.EncodeToString(longBytes) 331 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"%s","Bool":true,"Bytes":"%s"}}]}`, num, longString, longBytesBase64) 332 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect403(uniques.ErrUniqueValueTooLong.Error())).Println() 333 } 334 335 func TestBasicUsage_UNIQUEFIELD(t *testing.T) { 336 require := require.New(t) 337 vit := it.NewVIT(t, &it.SharedConfig_App1) 338 defer vit.TearDown() 339 340 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 341 num, bts := getUniqueNumber(vit) 342 343 // insert an initial record 344 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraintsOldAndNewUniques","Int":%d,"Str":"%s"}}]}`, num, bts) 345 expectedRecID := vit.PostWS(ws, "c.sys.CUD", body).NewID() 346 347 // fire the UNIQUEFIELD violation, avoid UNIQUE (Str) violation 348 _, newBts := getUniqueNumber(vit) 349 body = fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraintsOldAndNewUniques","Int":%d,"Str":"%s"}}]}`, num, newBts) 350 vit.PostWS(ws, "c.sys.CUD", body, coreutils.Expect409(it.QNameApp1_DocConstraintsOldAndNewUniques.String())) 351 352 t.Run("get record id by uniquefield value", func(t *testing.T) { 353 as, err := vit.AppStructs(istructs.AppQName_test1_app1) 354 require.NoError(err) 355 recordID, err := uniques.GetRecordIDByUniqueCombination(ws.WSID, it.QNameApp1_DocConstraintsOldAndNewUniques, as, map[string]interface{}{ 356 "Int": int32(num), 357 }) 358 require.NoError(err) 359 require.Equal(istructs.RecordID(expectedRecID), recordID) 360 }) 361 } 362 363 func TestRecordByUniqueValuesErrors(t *testing.T) { 364 require := require.New(t) 365 vit := it.NewVIT(t, &it.SharedConfig_App1) 366 defer vit.TearDown() 367 368 ws := vit.WS(istructs.AppQName_test1_app1, "test_ws") 369 num, bts := getUniqueNumber(vit) 370 371 body := fmt.Sprintf(`{"cuds":[{"fields":{"sys.ID":1,"sys.QName":"app1pkg.DocConstraints","Int":%d,"Str":"str","Bool":true,"Bytes":"%s"}}]}`, num, bts) 372 vit.PostWS(ws, "c.sys.CUD", body) 373 374 as, err := vit.AppStructs(istructs.AppQName_test1_app1) 375 require.NoError(err) 376 377 btsBytes := bytes.NewBuffer(nil) 378 binary.Write(btsBytes, binary.BigEndian, uint32(num)) 379 380 t.Run("unique does not exist by set of fields", func(t *testing.T) { 381 _, err = uniques.GetRecordIDByUniqueCombination(ws.WSID, it.QNameApp1_DocConstraints, as, nil) 382 require.ErrorIs(err, uniques.ErrUniqueNotExist) 383 384 { 385 _, err = uniques.GetRecordIDByUniqueCombination(ws.WSID, it.QNameApp1_DocConstraints, as, map[string]interface{}{ 386 "Str": "str", 387 "Bytes": btsBytes.Bytes(), 388 "Int": int32(num), 389 }) 390 require.ErrorIs(err, uniques.ErrUniqueNotExist) 391 } 392 393 { 394 _, err = uniques.GetRecordIDByUniqueCombination(ws.WSID, it.QNameApp1_DocConstraints, as, map[string]interface{}{ 395 "Str": "str", 396 "Bytes": btsBytes.Bytes(), 397 "Int": int32(num), 398 "Bool": true, 399 "Another": "str", 400 }) 401 require.ErrorIs(err, uniques.ErrUniqueNotExist) 402 } 403 404 { 405 _, err = uniques.GetRecordIDByUniqueCombination(ws.WSID, it.QNameApp1_DocConstraints, as, map[string]interface{}{ 406 "Str": "str", 407 "Bytes": btsBytes.Bytes(), 408 "Int": int32(num), 409 "Another": "str", 410 }) 411 require.ErrorIs(err, uniques.ErrUniqueNotExist) 412 } 413 }) 414 415 t.Run("wrong value type", func(t *testing.T) { 416 _, err = uniques.GetRecordIDByUniqueCombination(ws.WSID, it.QNameApp1_DocConstraints, as, map[string]interface{}{ 417 "Str": 42, 418 "Bytes": btsBytes.Bytes(), 419 "Int": int32(num), 420 "Bool": true, 421 }) 422 require.ErrorIs(err, appdef.ErrInvalidError) 423 }) 424 425 t.Run("wrong type", func(t *testing.T) { 426 t.Run("not found", func(t *testing.T) { 427 _, err = uniques.GetRecordIDByUniqueCombination(ws.WSID, appdef.NewQName("app1pkg", "unknown"), as, map[string]interface{}{ 428 "Str": 42, 429 }) 430 require.ErrorIs(err, appdef.ErrNotFoundError) 431 }) 432 t.Run("not a table", func(t *testing.T) { 433 _, err = uniques.GetRecordIDByUniqueCombination(ws.WSID, appdef.NewQName("app1pkg", "RatedQryParams"), as, map[string]interface{}{ 434 "Str": 42, 435 }) 436 require.ErrorIs(err, appdef.ErrInvalidError) 437 }) 438 }) 439 440 }