Chore: Use IAM DBTime utility in dashboard migrator (#112589)
This commit is contained in:
@@ -15,7 +15,6 @@ import (
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
|
||||
dashboardOG "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard"
|
||||
dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
|
||||
dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
||||
@@ -293,12 +292,12 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo
|
||||
var orgId int64
|
||||
var folder_uid sql.NullString
|
||||
var title string
|
||||
var updated time.Time
|
||||
var updated legacysql.DBTime
|
||||
var updatedBy sql.NullString
|
||||
var updatedByID sql.NullInt64
|
||||
var deleted sql.NullTime
|
||||
|
||||
var created time.Time
|
||||
var created legacysql.DBTime
|
||||
var createdBy sql.NullString
|
||||
var createdByID sql.NullInt64
|
||||
var message sql.NullString
|
||||
@@ -338,13 +337,13 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo
|
||||
dash.Namespace = a.namespacer(orgId)
|
||||
dash.APIVersion = fmt.Sprintf("%s/%s", dashboardV1.GROUP, apiVersion.String)
|
||||
dash.UID = gapiutil.CalculateClusterWideUID(dash)
|
||||
dash.SetCreationTimestamp(metav1.NewTime(created))
|
||||
dash.SetCreationTimestamp(metav1.NewTime(created.Time))
|
||||
meta, err := utils.MetaAccessor(dash)
|
||||
if err != nil {
|
||||
a.log.Debug("failed to get meta accessor for dashboard", "error", err, "uid", dash.UID, "name", dash.Name, "version", version)
|
||||
return nil, err
|
||||
}
|
||||
meta.SetUpdatedTimestamp(&updated)
|
||||
meta.SetUpdatedTimestamp(&updated.Time)
|
||||
meta.SetCreatedBy(getUserID(createdBy, createdByID))
|
||||
meta.SetUpdatedBy(getUserID(updatedBy, updatedByID))
|
||||
meta.SetDeprecatedInternalID(dashboard_id) //nolint:staticcheck
|
||||
|
||||
@@ -121,8 +121,8 @@ type CreateServiceAccountCommand struct {
|
||||
Role string
|
||||
IsDisabled bool
|
||||
OrgID int64
|
||||
Created DBTime
|
||||
Updated DBTime
|
||||
Created legacysql.DBTime
|
||||
Updated legacysql.DBTime
|
||||
LastSeenAt time.Time
|
||||
}
|
||||
|
||||
@@ -335,8 +335,8 @@ func (s *legacySQLStore) CreateServiceAccount(ctx context.Context, ns claims.Nam
|
||||
now := time.Now().UTC()
|
||||
lastSeenAt := now.AddDate(-10, 0, 0) // Set last seen 10 years ago like in user service
|
||||
|
||||
cmd.Created = NewDBTime(now)
|
||||
cmd.Updated = NewDBTime(now)
|
||||
cmd.Created = legacysql.NewDBTime(now)
|
||||
cmd.Updated = legacysql.NewDBTime(now)
|
||||
cmd.LastSeenAt = lastSeenAt
|
||||
|
||||
if ns.OrgID == 0 {
|
||||
|
||||
@@ -2,11 +2,9 @@ package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"embed"
|
||||
"fmt"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||
@@ -67,55 +65,3 @@ func mustTemplate(filename string) *template.Template {
|
||||
}
|
||||
panic(fmt.Sprintf("template file not found: %s", filename))
|
||||
}
|
||||
|
||||
type DBTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func NewDBTime(t time.Time) DBTime {
|
||||
return DBTime{Time: t}
|
||||
}
|
||||
|
||||
func (t DBTime) Value() (driver.Value, error) {
|
||||
if t.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return t.Format(time.DateTime), nil
|
||||
}
|
||||
|
||||
func (t DBTime) String() string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return t.Format(time.DateTime)
|
||||
}
|
||||
|
||||
func (t *DBTime) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
t.Time = time.Time{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var parsedTime time.Time
|
||||
var err error
|
||||
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
parsedTime, err = time.Parse(time.DateTime, string(v))
|
||||
case string:
|
||||
parsedTime, err = time.Parse(time.DateTime, v)
|
||||
case time.Time:
|
||||
parsedTime = v
|
||||
default:
|
||||
return fmt.Errorf("could not scan type %T into DBTime", value)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse time: %w", err)
|
||||
}
|
||||
|
||||
t.Time = parsedTime
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -215,8 +215,8 @@ func TestIdentityQueries(t *testing.T) {
|
||||
Data: createTeamMember(&CreateTeamMemberCommand{
|
||||
TeamID: 1,
|
||||
UserID: 1,
|
||||
Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
External: false,
|
||||
Permission: team.PermissionTypeMember,
|
||||
}),
|
||||
@@ -226,8 +226,8 @@ func TestIdentityQueries(t *testing.T) {
|
||||
Data: createTeamMember(&CreateTeamMemberCommand{
|
||||
TeamID: 1,
|
||||
UserID: 1,
|
||||
Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
External: false,
|
||||
Permission: team.PermissionTypeAdmin,
|
||||
}),
|
||||
@@ -425,8 +425,8 @@ func TestIdentityQueries(t *testing.T) {
|
||||
Email: "team1@example.com",
|
||||
IsProvisioned: false,
|
||||
OrgID: 1,
|
||||
Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -438,8 +438,8 @@ func TestIdentityQueries(t *testing.T) {
|
||||
IsProvisioned: true,
|
||||
ExternalUID: "team-2-uid",
|
||||
OrgID: 1,
|
||||
Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -452,7 +452,7 @@ func TestIdentityQueries(t *testing.T) {
|
||||
Email: "team1@example.com",
|
||||
IsProvisioned: true,
|
||||
ExternalUID: "team-1-uid",
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -471,8 +471,8 @@ func TestIdentityQueries(t *testing.T) {
|
||||
OrgID: 1,
|
||||
UserID: 123,
|
||||
Role: "Viewer",
|
||||
Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -481,8 +481,8 @@ func TestIdentityQueries(t *testing.T) {
|
||||
OrgID: 2,
|
||||
UserID: 456,
|
||||
Role: "Admin",
|
||||
Created: NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -501,9 +501,9 @@ func TestIdentityQueries(t *testing.T) {
|
||||
IsProvisioned: false,
|
||||
Salt: "randomsalt",
|
||||
Rands: "randomrands",
|
||||
Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
LastSeenAt: NewDBTime(time.Date(2013, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
LastSeenAt: legacysql.NewDBTime(time.Date(2013, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Role: "Viewer",
|
||||
}),
|
||||
},
|
||||
@@ -521,9 +521,9 @@ func TestIdentityQueries(t *testing.T) {
|
||||
IsProvisioned: true,
|
||||
Salt: "adminsalt",
|
||||
Rands: "adminrands",
|
||||
Created: NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
LastSeenAt: NewDBTime(time.Date(2013, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
LastSeenAt: legacysql.NewDBTime(time.Date(2013, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Role: "Admin",
|
||||
}),
|
||||
},
|
||||
@@ -538,8 +538,8 @@ func TestIdentityQueries(t *testing.T) {
|
||||
Login: "sa-1-service-account-1",
|
||||
IsDisabled: false,
|
||||
OrgID: 1,
|
||||
Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)),
|
||||
LastSeenAt: time.Date(2013, 1, 1, 12, 0, 0, 0, time.UTC),
|
||||
}),
|
||||
},
|
||||
@@ -552,8 +552,8 @@ func TestIdentityQueries(t *testing.T) {
|
||||
Login: "sa-2-disabled-service-account",
|
||||
IsDisabled: true,
|
||||
OrgID: 2,
|
||||
Created: NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Updated: NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Created: legacysql.NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 2, 1, 10, 30, 0, 0, time.UTC)),
|
||||
LastSeenAt: time.Date(2013, 2, 1, 10, 30, 0, 0, time.UTC),
|
||||
}),
|
||||
},
|
||||
@@ -570,7 +570,7 @@ func TestIdentityQueries(t *testing.T) {
|
||||
IsDisabled: true,
|
||||
EmailVerified: false,
|
||||
Role: "Editor",
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 13, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 13, 0, 0, 0, time.UTC)),
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -581,7 +581,7 @@ func TestIdentityQueries(t *testing.T) {
|
||||
OrgID: 1,
|
||||
UserID: 123,
|
||||
Role: "Admin",
|
||||
Updated: NewDBTime(time.Date(2023, 1, 1, 14, 0, 0, 0, time.UTC)),
|
||||
Updated: legacysql.NewDBTime(time.Date(2023, 1, 1, 14, 0, 0, 0, time.UTC)),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -180,8 +180,8 @@ type CreateTeamCommand struct {
|
||||
UID string
|
||||
Name string
|
||||
OrgID int64
|
||||
Created DBTime
|
||||
Updated DBTime
|
||||
Created legacysql.DBTime
|
||||
Updated legacysql.DBTime
|
||||
Email string
|
||||
ExternalID string
|
||||
IsProvisioned bool
|
||||
@@ -215,8 +215,8 @@ func (r createTeamQuery) Validate() error {
|
||||
func (s *legacySQLStore) CreateTeam(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamCommand) (*CreateTeamResult, error) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
cmd.Created = NewDBTime(now)
|
||||
cmd.Updated = NewDBTime(now)
|
||||
cmd.Created = legacysql.NewDBTime(now)
|
||||
cmd.Updated = legacysql.NewDBTime(now)
|
||||
cmd.OrgID = ns.OrgID
|
||||
|
||||
if cmd.OrgID == 0 {
|
||||
@@ -267,7 +267,7 @@ func (s *legacySQLStore) CreateTeam(ctx context.Context, ns claims.NamespaceInfo
|
||||
type UpdateTeamCommand struct {
|
||||
UID string
|
||||
Name string
|
||||
Updated DBTime
|
||||
Updated legacysql.DBTime
|
||||
Email string
|
||||
ExternalID string
|
||||
IsProvisioned bool
|
||||
@@ -301,7 +301,7 @@ func (r updateTeamQuery) Validate() error {
|
||||
func (s *legacySQLStore) UpdateTeam(ctx context.Context, ns claims.NamespaceInfo, cmd UpdateTeamCommand) (*UpdateTeamResult, error) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
cmd.Updated = NewDBTime(now)
|
||||
cmd.Updated = legacysql.NewDBTime(now)
|
||||
|
||||
sql, err := s.sql(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -132,8 +132,8 @@ type CreateTeamMemberCommand struct {
|
||||
UserID int64
|
||||
UserUID string
|
||||
OrgID int64
|
||||
Created DBTime
|
||||
Updated DBTime
|
||||
Created legacysql.DBTime
|
||||
Updated legacysql.DBTime
|
||||
External bool
|
||||
Permission team.PermissionType
|
||||
}
|
||||
@@ -164,8 +164,8 @@ func (r createTeamMemberQuery) Validate() error {
|
||||
|
||||
func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamMemberCommand) (*CreateTeamMemberResult, error) {
|
||||
now := time.Now().UTC()
|
||||
cmd.Created = NewDBTime(now)
|
||||
cmd.Updated = NewDBTime(now)
|
||||
cmd.Created = legacysql.NewDBTime(now)
|
||||
cmd.Updated = legacysql.NewDBTime(now)
|
||||
cmd.OrgID = ns.OrgID
|
||||
|
||||
if cmd.OrgID == 0 {
|
||||
|
||||
@@ -303,9 +303,9 @@ type CreateUserCommand struct {
|
||||
IsProvisioned bool
|
||||
Salt string
|
||||
Rands string
|
||||
Created DBTime
|
||||
Updated DBTime
|
||||
LastSeenAt DBTime
|
||||
Created legacysql.DBTime
|
||||
Updated legacysql.DBTime
|
||||
LastSeenAt legacysql.DBTime
|
||||
Role string
|
||||
}
|
||||
|
||||
@@ -317,15 +317,15 @@ type CreateOrgUserCommand struct {
|
||||
OrgID int64
|
||||
UserID int64
|
||||
Role string
|
||||
Created DBTime
|
||||
Updated DBTime
|
||||
Created legacysql.DBTime
|
||||
Updated legacysql.DBTime
|
||||
}
|
||||
|
||||
type UpdateOrgUserCommand struct {
|
||||
OrgID int64
|
||||
UserID int64
|
||||
Role string
|
||||
Updated DBTime
|
||||
Updated legacysql.DBTime
|
||||
}
|
||||
|
||||
type DeleteUserCommand struct {
|
||||
@@ -395,9 +395,9 @@ func (s *legacySQLStore) CreateUser(ctx context.Context, ns claims.NamespaceInfo
|
||||
|
||||
cmd.Salt = salt
|
||||
cmd.Rands = rands
|
||||
cmd.Created = NewDBTime(now)
|
||||
cmd.Updated = NewDBTime(now)
|
||||
cmd.LastSeenAt = NewDBTime(lastSeenAt)
|
||||
cmd.Created = legacysql.NewDBTime(now)
|
||||
cmd.Updated = legacysql.NewDBTime(now)
|
||||
cmd.LastSeenAt = legacysql.NewDBTime(lastSeenAt)
|
||||
|
||||
sql, err := s.sql(ctx)
|
||||
if err != nil {
|
||||
@@ -616,7 +616,7 @@ type UpdateUserCommand struct {
|
||||
IsDisabled bool
|
||||
EmailVerified bool
|
||||
Role string
|
||||
Updated DBTime
|
||||
Updated legacysql.DBTime
|
||||
}
|
||||
|
||||
type UpdateUserResult struct {
|
||||
@@ -662,7 +662,7 @@ func (r updateOrgUserQuery) Validate() error {
|
||||
// UpdateUser implements LegacyIdentityStore.
|
||||
func (s *legacySQLStore) UpdateUser(ctx context.Context, ns claims.NamespaceInfo, cmd UpdateUserCommand) (*UpdateUserResult, error) {
|
||||
now := time.Now().UTC()
|
||||
cmd.Updated = NewDBTime(now)
|
||||
cmd.Updated = legacysql.NewDBTime(now)
|
||||
|
||||
sql, err := s.sql(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package legacysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DBTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func NewDBTime(t time.Time) DBTime {
|
||||
return DBTime{Time: t}
|
||||
}
|
||||
|
||||
func (t DBTime) Value() (driver.Value, error) {
|
||||
if t.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return t.Format(time.DateTime), nil
|
||||
}
|
||||
|
||||
func (t DBTime) String() string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return t.Format(time.DateTime)
|
||||
}
|
||||
|
||||
func (t *DBTime) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
t.Time = time.Time{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var parsedTime time.Time
|
||||
var err error
|
||||
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
parsedTime, err = time.Parse(time.DateTime, string(v))
|
||||
case string:
|
||||
parsedTime, err = time.Parse(time.DateTime, v)
|
||||
case time.Time:
|
||||
parsedTime = v
|
||||
default:
|
||||
return fmt.Errorf("could not scan type %T into DBTime", value)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse time: %w", err)
|
||||
}
|
||||
|
||||
t.Time = parsedTime
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package legacysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDBTime_ValueAndString_ZeroAndNonZero(t *testing.T) {
|
||||
zero := NewDBTime(time.Time{})
|
||||
if v, err := zero.Value(); err != nil {
|
||||
t.Fatalf("unexpected error for zero Value: %v", err)
|
||||
} else if v != nil {
|
||||
t.Fatalf("expected nil driver.Value for zero time, got %#v", v)
|
||||
}
|
||||
if s := zero.String(); s != "" {
|
||||
t.Fatalf("expected empty string for zero DBTime.String(), got %q", s)
|
||||
}
|
||||
|
||||
tt := time.Date(2023, 10, 5, 13, 14, 15, 0, time.UTC)
|
||||
dt := NewDBTime(tt)
|
||||
|
||||
val, err := dt.Value()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for non-zero Value: %v", err)
|
||||
}
|
||||
strVal, ok := val.(string)
|
||||
if !ok {
|
||||
t.Fatalf("expected Value to be string, got %T", val)
|
||||
}
|
||||
expected := tt.Format(time.DateTime)
|
||||
if strVal != expected {
|
||||
t.Fatalf("Value() = %q, want %q", strVal, expected)
|
||||
}
|
||||
|
||||
if s := dt.String(); s != expected {
|
||||
t.Fatalf("String() = %q, want %q", s, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBTime_Scan_VariousInputs(t *testing.T) {
|
||||
base := time.Date(2022, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||
layout := time.DateTime
|
||||
formatted := base.Format(layout)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
wantTime time.Time
|
||||
wantIsZero bool
|
||||
expectError bool
|
||||
}{
|
||||
{"nil sets zero", nil, time.Time{}, true, false},
|
||||
{"scan from string", formatted, base, false, false},
|
||||
{"scan from []byte", []byte(formatted), base, false, false},
|
||||
{"scan from time.Time", base, base, false, false},
|
||||
{"invalid parse", "not-a-time", time.Time{}, true, true},
|
||||
{"unsupported type", 12345, time.Time{}, true, true},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var dt DBTime
|
||||
err := dt.Scan(tc.input)
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
// when expecting error, no further checks
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if tc.wantIsZero {
|
||||
if !dt.IsZero() {
|
||||
t.Fatalf("expected DBTime to be zero, got %v", dt.Time)
|
||||
}
|
||||
return
|
||||
}
|
||||
if dt.IsZero() {
|
||||
t.Fatalf("expected non-zero time, got zero")
|
||||
}
|
||||
// Compare times using Equal to ignore monotonic clock bits
|
||||
if !dt.Equal(tc.wantTime) {
|
||||
t.Fatalf("scanned time = %v, want %v", dt.Time, tc.wantTime)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBTime_Scan_ParseErrorDetails(t *testing.T) {
|
||||
var dt DBTime
|
||||
err := dt.Scan("garbage")
|
||||
if err == nil {
|
||||
t.Fatalf("expected parse error, got nil")
|
||||
}
|
||||
// error message should mention parse
|
||||
if !contains(err.Error(), "could not parse time") {
|
||||
t.Fatalf("unexpected error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBTime_Scan_UnsupportedTypeMessage(t *testing.T) {
|
||||
var dt DBTime
|
||||
typ := struct{}{}
|
||||
err := dt.Scan(typ)
|
||||
if err == nil {
|
||||
t.Fatalf("expected unsupported-type error, got nil")
|
||||
}
|
||||
want := fmt.Sprintf("could not scan type %T into DBTime", typ)
|
||||
if err.Error() != want {
|
||||
t.Fatalf("error = %q, want %q", err.Error(), want)
|
||||
}
|
||||
}
|
||||
|
||||
// small helper to avoid importing strings for a single contains check
|
||||
func contains(s, substr string) bool {
|
||||
return len(substr) == 0 || (len(s) >= len(substr) && indexOf(s, substr) >= 0)
|
||||
}
|
||||
|
||||
func indexOf(s, substr string) int {
|
||||
n := len(s)
|
||||
m := len(substr)
|
||||
if m == 0 {
|
||||
return 0
|
||||
}
|
||||
for i := 0; i <= n-m; i++ {
|
||||
if s[i:i+m] == substr {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
Reference in New Issue
Block a user