// Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package bson import ( "encoding/binary" "encoding/hex" "encoding/json" "fmt" "testing" "time" "gitea.psichedelico.com/go/bson/internal/assert" "gitea.psichedelico.com/go/bson/internal/require" ) func TestString(t *testing.T) { id := NewObjectID() require.Contains(t, id.String(), id.Hex()) } func BenchmarkHex(b *testing.B) { id := NewObjectID() for i := 0; i < b.N; i++ { id.Hex() } } func BenchmarkObjectIDFromHex(b *testing.B) { id := NewObjectID().Hex() for i := 0; i < b.N; i++ { _, _ = ObjectIDFromHex(id) } } func BenchmarkNewObjectIDFromTimestamp(b *testing.B) { for i := 0; i < b.N; i++ { timestamp := time.Now().Add(time.Duration(i) * time.Millisecond) _ = NewObjectIDFromTimestamp(timestamp) } } func BenchmarkObjectIDJSON(b *testing.B) { b.Run("Marshal", func(b *testing.B) { id := NewObjectID() data, err := id.MarshalJSON() if err != nil { b.Fatal(err) } b.SetBytes(int64(len(data))) b.ReportAllocs() for i := 0; i < b.N; i++ { _, _ = id.MarshalJSON() } }) b.Run("Unmarshal", func(b *testing.B) { id := NewObjectID() data, err := id.MarshalJSON() if err != nil { b.Fatal(err) } b.SetBytes(int64(len(data))) b.ReportAllocs() for i := 0; i < b.N; i++ { var v ObjectID if err := v.UnmarshalJSON(data); err != nil { b.Fatal(err) } } }) b.Run("UnmarshalOID", func(b *testing.B) { data, err := json.Marshal(map[string]string{ "$oid": NewObjectID().Hex(), }) if err != nil { b.Fatal(err) } b.SetBytes(int64(len(data))) b.ReportAllocs() for i := 0; i < b.N; i++ { var v ObjectID if err := v.UnmarshalJSON(data); err != nil { b.Fatal(err) } } }) } func TestFromHex_RoundTrip(t *testing.T) { before := NewObjectID() after, err := ObjectIDFromHex(before.Hex()) require.NoError(t, err) require.Equal(t, before, after) } func TestFromHex_InvalidHex(t *testing.T) { _, err := ObjectIDFromHex("this is not a valid hex string!") require.Error(t, err) } func TestFromHex_WrongLength(t *testing.T) { _, err := ObjectIDFromHex("deadbeef") require.Equal(t, ErrInvalidHex, err) } func TestTimeStamp(t *testing.T) { testCases := []struct { Hex string Expected string }{ { "000000001111111111111111", "1970-01-01 00:00:00 +0000 UTC", }, { "7FFFFFFF1111111111111111", "2038-01-19 03:14:07 +0000 UTC", }, { "800000001111111111111111", "2038-01-19 03:14:08 +0000 UTC", }, { "FFFFFFFF1111111111111111", "2106-02-07 06:28:15 +0000 UTC", }, } for _, testcase := range testCases { id, err := ObjectIDFromHex(testcase.Hex) require.NoError(t, err) secs := int64(binary.BigEndian.Uint32(id[0:4])) timestamp := time.Unix(secs, 0).UTC() require.Equal(t, testcase.Expected, timestamp.String()) } } func TestCreateFromTime(t *testing.T) { testCases := []struct { time string Expected string }{ { "1970-01-01T00:00:00.000Z", "00000000", }, { "2038-01-19T03:14:07.000Z", "7fffffff", }, { "2038-01-19T03:14:08.000Z", "80000000", }, { "2106-02-07T06:28:15.000Z", "ffffffff", }, } layout := "2006-01-02T15:04:05.000Z" for _, testcase := range testCases { time, err := time.Parse(layout, testcase.time) require.NoError(t, err) id := NewObjectIDFromTimestamp(time) timeStr := hex.EncodeToString(id[0:4]) require.Equal(t, testcase.Expected, timeStr) } } func TestGenerationTime(t *testing.T) { testCases := []struct { hex string Expected string }{ { "000000001111111111111111", "1970-01-01 00:00:00 +0000 UTC", }, { "7FFFFFFF1111111111111111", "2038-01-19 03:14:07 +0000 UTC", }, { "800000001111111111111111", "2038-01-19 03:14:08 +0000 UTC", }, { "FFFFFFFF1111111111111111", "2106-02-07 06:28:15 +0000 UTC", }, } for _, testcase := range testCases { id, err := ObjectIDFromHex(testcase.hex) require.NoError(t, err) genTime := id.Timestamp() require.Equal(t, testcase.Expected, genTime.String()) } } func TestCounterOverflow(t *testing.T) { objectIDCounter = 0xFFFFFFFF NewObjectID() require.Equal(t, uint32(0), objectIDCounter) } func TestObjectID_MarshalJSONMap(t *testing.T) { type mapOID struct { Map map[ObjectID]string } oid := NewObjectID() expectedJSON := []byte(fmt.Sprintf(`{"Map":{%q:"foo"}}`, oid.Hex())) data := mapOID{ Map: map[ObjectID]string{oid: "foo"}, } out, err := json.Marshal(&data) require.NoError(t, err) require.Equal(t, expectedJSON, out) } func TestObjectID_UnmarshalJSONMap(t *testing.T) { type mapOID struct { Map map[ObjectID]string } oid := NewObjectID() mapOIDJSON := []byte(fmt.Sprintf(`{"Map":{%q:"foo"}}`, oid.Hex())) expectedData := mapOID{ Map: map[ObjectID]string{oid: "foo"}, } data := mapOID{} err := json.Unmarshal(mapOIDJSON, &data) require.NoError(t, err) require.Equal(t, expectedData, data) } func TestObjectID_UnmarshalJSON(t *testing.T) { oid := NewObjectID() hexJSON := fmt.Sprintf(`{"foo": %q}`, oid.Hex()) extJSON := fmt.Sprintf(`{"foo": {"$oid": %q}}`, oid.Hex()) emptyStringJSON := `{"foo": ""}` nullJSON := `{"foo": null}` testCases := []struct { name string jsonString string expected ObjectID }{ {"hex bytes", hexJSON, oid}, {"extended JSON", extJSON, oid}, {"empty string", emptyStringJSON, NilObjectID}, {"null", nullJSON, NilObjectID}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var got map[string]ObjectID err := json.Unmarshal([]byte(tc.jsonString), &got) assert.Nil(t, err, "Unmarshal error: %v", err) gotOid := got["foo"] assert.Equal(t, tc.expected, gotOid, "expected ObjectID %s, got %s", tc.expected, gotOid) }) } } func TestObjectID_MarshalText(t *testing.T) { oid := ObjectID{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB} b, err := oid.MarshalText() assert.Nil(t, err, "MarshalText error: %v", err) want := "000102030405060708090a0b" got := string(b) assert.Equal(t, want, got, "want %v, got %v", want, got) } func TestObjectID_UnmarshalText(t *testing.T) { var oid ObjectID err := oid.UnmarshalText([]byte("000102030405060708090a0b")) assert.Nil(t, err, "UnmarshalText error: %v", err) want := ObjectID{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB} assert.Equal(t, want, oid, "want %v, got %v", want, oid) }