@@ -40,6 +40,7 @@ func TestGenerator(t *testing.T) {
4040 t .Run ("NewV5" , testNewV5 )
4141 t .Run ("NewV6" , testNewV6 )
4242 t .Run ("NewV7" , testNewV7 )
43+ t .Run ("NewV8" , testNewV8 )
4344}
4445
4546func testNewV1 (t * testing.T ) {
@@ -1174,3 +1175,173 @@ func testErrCheck(t *testing.T, name string, errContains string, err error) bool
11741175
11751176 return true
11761177}
1178+
1179+ func testNewV8 (t * testing.T ) {
1180+ t .Run ("Basic" , makeTestNewV8Basic ())
1181+ t .Run ("VersionAndVariant" , makeTestNewV8VersionAndVariant ())
1182+ t .Run ("CustomFields" , makeTestNewV8CustomFields ())
1183+ t .Run ("InvalidLength" , makeTestNewV8InvalidLength ())
1184+ }
1185+
1186+ func makeTestNewV8Basic () func (t * testing.T ) {
1187+ return func (t * testing.T ) {
1188+ customA := []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 }
1189+ customB := []byte {0x07 , 0x08 }
1190+ customC := []byte {0x09 , 0x0a , 0x0b , 0x0c , 0x0d , 0x0e , 0x0f , 0x10 }
1191+
1192+ u , err := NewV8 (customA , customB , customC )
1193+ if err != nil {
1194+ t .Fatal (err )
1195+ }
1196+ if u == Nil {
1197+ t .Error ("UUID is nil" )
1198+ }
1199+ }
1200+ }
1201+
1202+ func makeTestNewV8VersionAndVariant () func (t * testing.T ) {
1203+ return func (t * testing.T ) {
1204+ customA := []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 }
1205+ customB := []byte {0x07 , 0x08 }
1206+ customC := []byte {0x09 , 0x0a , 0x0b , 0x0c , 0x0d , 0x0e , 0x0f , 0x10 }
1207+
1208+ u , err := NewV8 (customA , customB , customC )
1209+ if err != nil {
1210+ t .Fatal (err )
1211+ }
1212+ if got , want := u .Version (), V8 ; got != want {
1213+ t .Errorf ("got version %d, want %d" , got , want )
1214+ }
1215+ if got , want := u .Variant (), VariantRFC9562 ; got != want {
1216+ t .Errorf ("got variant %d, want %d" , got , want )
1217+ }
1218+ }
1219+ }
1220+
1221+ func makeTestNewV8CustomFields () func (t * testing.T ) {
1222+ return func (t * testing.T ) {
1223+ // Test that custom data is correctly placed in the UUID
1224+ // customA: 48 bits = 6 bytes -> u[0:6]
1225+ // customB: 12 bits -> lower 12 bits of u[6:8] (high 4 bits are version)
1226+ // customC: 62 bits -> lower 62 bits of u[8:16] (high 2 bits are variant)
1227+ customA := []byte {0xAA , 0xBB , 0xCC , 0xDD , 0xEE , 0xFF }
1228+ customB := []byte {0x01 , 0x23 } // 0x0123; only the lower 12 bits (0x123) are used
1229+ customC := []byte {0x11 , 0x22 , 0x33 , 0x44 , 0x55 , 0x66 , 0x77 , 0x88 }
1230+
1231+ u , err := NewV8 (customA , customB , customC )
1232+ if err != nil {
1233+ t .Fatal (err )
1234+ }
1235+
1236+ // Check customA bytes
1237+ if ! bytes .Equal (u [0 :6 ], customA ) {
1238+ t .Errorf ("customA mismatch: got %x, want %x" , u [0 :6 ], customA )
1239+ }
1240+
1241+ // Check version is set correctly (high nibble of byte 6)
1242+ if u [6 ]>> 4 != V8 {
1243+ t .Errorf ("version bits incorrect: got %d, want %d" , u [6 ]>> 4 , V8 )
1244+ }
1245+
1246+ // Check customB lower 12 bits (low nibble of u[6] and all of u[7])
1247+ bLow := (uint16 (u [6 ]& 0x0f ) << 8 ) | uint16 (u [7 ])
1248+ if bLow != 0x123 {
1249+ t .Errorf ("customB bits incorrect: got %x, want %x" , bLow , 0x123 )
1250+ }
1251+
1252+ // Check variant is set correctly (high 2 bits of byte 8)
1253+ if (u [8 ] >> 6 ) != 0x02 {
1254+ t .Errorf ("variant bits incorrect: got %x, want %x" , u [8 ]>> 6 , 0x02 )
1255+ }
1256+
1257+ // Check customC lower 62 bits are preserved (excluding variant bits)
1258+ wantC8 := (customC [0 ] & 0x3f ) // variant overwrites top 2 bits
1259+ // Actually the implementation masks u[8] before setting variant
1260+ // So we expect the lower 6 bits of customC[0] to be in u[8], then variant added
1261+ gotC8Lower := u [8 ] & 0x3f
1262+ if gotC8Lower != wantC8 {
1263+ t .Errorf ("customC[0] lower bits incorrect: got %x, want %x" , gotC8Lower , wantC8 )
1264+ }
1265+ if ! bytes .Equal (u [9 :16 ], customC [1 :8 ]) {
1266+ t .Errorf ("customC[1:8] mismatch: got %x, want %x" , u [9 :16 ], customC [1 :8 ])
1267+ }
1268+ }
1269+ }
1270+
1271+ func makeTestNewV8InvalidLength () func (t * testing.T ) {
1272+ return func (t * testing.T ) {
1273+ // Test that incorrect lengths return errors
1274+ tests := []struct {
1275+ name string
1276+ customA []byte
1277+ customB []byte
1278+ customC []byte
1279+ errMsg string
1280+ }{
1281+ {
1282+ name : "customA too short" ,
1283+ customA : []byte {0x01 , 0x02 }, // 2 bytes instead of 6
1284+ customB : []byte {0x01 , 0x23 },
1285+ customC : []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 },
1286+ errMsg : "customA must be exactly 6 bytes" ,
1287+ },
1288+ {
1289+ name : "customA too long" ,
1290+ customA : []byte {0xFF , 0xFF , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 }, // 8 bytes
1291+ customB : []byte {0x01 , 0x23 },
1292+ customC : []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 },
1293+ errMsg : "customA must be exactly 6 bytes" ,
1294+ },
1295+ {
1296+ name : "customB too short" ,
1297+ customA : []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 },
1298+ customB : []byte {0x01 }, // 1 byte instead of 2
1299+ customC : []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 },
1300+ errMsg : "customB must be exactly 2 bytes" ,
1301+ },
1302+ {
1303+ name : "customB too long" ,
1304+ customA : []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 },
1305+ customB : []byte {0xFF , 0x01 , 0x23 }, // 3 bytes
1306+ customC : []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 },
1307+ errMsg : "customB must be exactly 2 bytes" ,
1308+ },
1309+ {
1310+ name : "customC too short" ,
1311+ customA : []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 },
1312+ customB : []byte {0x01 , 0x23 },
1313+ customC : []byte {0x01 , 0x02 }, // 2 bytes instead of 8
1314+ errMsg : "customC must be exactly 8 bytes" ,
1315+ },
1316+ {
1317+ name : "customC too long" ,
1318+ customA : []byte {0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 },
1319+ customB : []byte {0x01 , 0x23 },
1320+ customC : []byte {0xFF , 0xFF , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 }, // 10 bytes
1321+ errMsg : "customC must be exactly 8 bytes" ,
1322+ },
1323+ {
1324+ name : "all empty" ,
1325+ customA : nil ,
1326+ customB : nil ,
1327+ customC : nil ,
1328+ errMsg : "customA must be exactly 6 bytes" ,
1329+ },
1330+ }
1331+
1332+ for _ , tc := range tests {
1333+ t .Run (tc .name , func (t * testing.T ) {
1334+ _ , err := NewV8 (tc .customA , tc .customB , tc .customC )
1335+ if err == nil {
1336+ t .Fatal ("expected error, got nil" )
1337+ }
1338+ if ! errors .Is (err , ErrV8FieldLength ) {
1339+ t .Errorf ("expected ErrV8FieldLength, got %v" , err )
1340+ }
1341+ if ! strings .Contains (err .Error (), tc .errMsg ) {
1342+ t .Errorf ("error message %q should contain %q" , err .Error (), tc .errMsg )
1343+ }
1344+ })
1345+ }
1346+ }
1347+ }
0 commit comments