coder/coderd/insights_internal_test.go

345 lines
9.1 KiB
Go

package coderd
import (
"context"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/codersdk"
)
func Test_parseInsightsStartAndEndTime(t *testing.T) {
t.Parallel()
t.Logf("machine location: %s", time.Now().Location())
layout := insightsTimeLayout
now := time.Now().UTC()
t.Logf("now: %s", now)
t.Logf("now location: %s", now.Location())
y, m, d := now.Date()
today := time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
t.Logf("today: %s", today)
thisHour := time.Date(y, m, d, now.Hour(), 0, 0, 0, time.UTC)
t.Logf("thisHour: %s", thisHour)
thisHourRoundUp := thisHour.Add(time.Hour)
t.Logf("thisHourRoundUp: %s", thisHourRoundUp)
helsinki, err := time.LoadLocation("Europe/Helsinki")
require.NoError(t, err)
type args struct {
startTime string
endTime string
}
tests := []struct {
name string
args args
wantStartTime time.Time
wantEndTime time.Time
wantOk bool
}{
{
name: "Same",
args: args{
startTime: "2023-07-10T00:00:00Z",
endTime: "2023-07-10T00:00:00Z",
},
wantStartTime: time.Date(2023, 7, 10, 0, 0, 0, 0, time.UTC),
wantEndTime: time.Date(2023, 7, 10, 0, 0, 0, 0, time.UTC),
wantOk: true,
},
{
name: "Week",
args: args{
startTime: "2023-07-10T00:00:00Z",
endTime: "2023-07-17T00:00:00Z",
},
wantStartTime: time.Date(2023, 7, 10, 0, 0, 0, 0, time.UTC),
wantEndTime: time.Date(2023, 7, 17, 0, 0, 0, 0, time.UTC),
wantOk: true,
},
{
name: "Today",
args: args{
startTime: today.Format(layout),
endTime: thisHour.Format(layout),
},
wantStartTime: time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC),
wantEndTime: time.Date(today.Year(), today.Month(), today.Day(), thisHour.Hour(), 0, 0, 0, time.UTC),
wantOk: true,
},
{
name: "Today with minutes and seconds",
args: args{
startTime: today.Format(layout),
endTime: thisHour.Add(time.Minute + time.Second).Format(layout),
},
wantOk: false,
},
{
name: "Today hour round up",
args: args{
startTime: today.Format(layout),
endTime: thisHourRoundUp.Format(layout),
},
wantStartTime: time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC),
wantEndTime: time.Date(thisHourRoundUp.Year(), thisHourRoundUp.Month(), thisHourRoundUp.Day(), thisHourRoundUp.Hour(), 0, 0, 0, time.UTC),
wantOk: true,
},
{
name: "Other timezone week",
args: args{
startTime: "2023-07-10T00:00:00+03:00",
endTime: "2023-07-17T00:00:00+03:00",
},
wantStartTime: time.Date(2023, 7, 10, 0, 0, 0, 0, helsinki),
wantEndTime: time.Date(2023, 7, 17, 0, 0, 0, 0, helsinki),
wantOk: true,
},
{
name: "Daylight savings time",
args: args{
startTime: "2023-03-26T00:00:00+02:00",
endTime: "2023-03-27T00:00:00+03:00",
},
wantStartTime: time.Date(2023, 3, 26, 0, 0, 0, 0, helsinki),
wantEndTime: time.Date(2023, 3, 27, 0, 0, 0, 0, helsinki),
wantOk: true,
},
{
name: "Bad format",
args: args{
startTime: "2023-07-10",
endTime: "2023-07-17",
},
wantOk: false,
},
{
name: "Zero time",
args: args{
startTime: (time.Time{}).Format(layout),
endTime: (time.Time{}).Format(layout),
},
wantOk: false,
},
{
name: "Time in future",
args: args{
startTime: today.AddDate(0, 0, 1).Format(layout),
endTime: today.AddDate(0, 0, 2).Format(layout),
},
wantOk: false,
},
{
name: "End before start",
args: args{
startTime: today.Format(layout),
endTime: today.AddDate(0, 0, -1).Format(layout),
},
wantOk: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
t.Log("startTime: ", tt.args.startTime)
t.Log("endTime: ", tt.args.endTime)
if tt.wantOk {
t.Log("wantStartTime: ", tt.wantStartTime)
t.Log("wantEndTime: ", tt.wantEndTime)
}
rw := httptest.NewRecorder()
gotStartTime, gotEndTime, gotOk := parseInsightsStartAndEndTime(context.Background(), rw, now, tt.args.startTime, tt.args.endTime)
if !assert.Equal(t, tt.wantOk, gotOk) {
//nolint:bodyclose
t.Log("Status: ", rw.Result().StatusCode)
t.Log("Body: ", rw.Body.String())
return
}
// assert.Equal is unable to test time equality with different
// (but same) locations because the *time.Location names differ
// between LoadLocation and Parse, so we use assert.WithinDuration.
assert.WithinDuration(t, tt.wantStartTime, gotStartTime, 0)
assert.True(t, tt.wantStartTime.Equal(gotStartTime))
assert.WithinDuration(t, tt.wantEndTime, gotEndTime, 0)
assert.True(t, tt.wantEndTime.Equal(gotEndTime))
})
}
}
func Test_parseInsightsInterval_week(t *testing.T) {
t.Parallel()
layout := insightsTimeLayout
sydneyLoc, err := time.LoadLocation("Australia/Sydney") // Random location
require.NoError(t, err)
now := time.Now().In(sydneyLoc)
t.Logf("now: %s", now)
y, m, d := now.Date()
today := time.Date(y, m, d, 0, 0, 0, 0, sydneyLoc)
t.Logf("today: %s", today)
thisHour := time.Date(y, m, d, now.Hour(), 0, 0, 0, sydneyLoc)
t.Logf("thisHour: %s", thisHour)
twoHoursAgo := thisHour.Add(-2 * time.Hour)
t.Logf("twoHoursAgo: %s", twoHoursAgo)
thirteenDaysAgo := today.AddDate(0, 0, -13)
t.Logf("thirteenDaysAgo: %s", thirteenDaysAgo)
sixDaysAgo := today.AddDate(0, 0, -6)
t.Logf("sixDaysAgo: %s", sixDaysAgo)
nineDaysAgo := today.AddDate(0, 0, -9)
t.Logf("nineDaysAgo: %s", nineDaysAgo)
type args struct {
startTime string
endTime string
}
tests := []struct {
name string
args args
wantOk bool
}{
{
name: "Two full weeks",
args: args{
startTime: "2023-08-10T00:00:00+02:00",
endTime: "2023-08-24T00:00:00+02:00",
},
wantOk: true,
},
{
name: "One full week",
args: args{
startTime: "2023-09-06T00:00:00+02:00",
endTime: "2023-09-13T00:00:00+02:00",
},
wantOk: true,
},
{
name: "6 days are acceptable",
args: args{
startTime: sixDaysAgo.Format(layout),
endTime: stripTime(thisHour).Format(layout),
},
wantOk: true,
},
{
name: "Shorter than a full week",
args: args{
startTime: "2023-09-08T00:00:00+02:00",
endTime: "2023-09-13T00:00:00+02:00",
},
wantOk: false,
},
{
name: "9 days (7 + 2) are not acceptable",
args: args{
startTime: nineDaysAgo.Format(layout),
endTime: stripTime(thisHour).Format(layout),
},
wantOk: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
t.Log("startTime: ", tt.args.startTime)
t.Log("endTime: ", tt.args.endTime)
rw := httptest.NewRecorder()
startTime, endTime, ok := parseInsightsStartAndEndTime(context.Background(), rw, now, tt.args.startTime, tt.args.endTime)
if !ok {
//nolint:bodyclose
t.Log("Status: ", rw.Result().StatusCode)
t.Log("Body: ", rw.Body.String())
}
require.True(t, ok, "start_time and end_time must be valid")
parsedInterval, gotOk := parseInsightsInterval(context.Background(), rw, "week", startTime, endTime)
if !assert.Equal(t, tt.wantOk, gotOk) {
//nolint:bodyclose
t.Log("Status: ", rw.Result().StatusCode)
t.Log("Body: ", rw.Body.String())
return
}
if tt.wantOk {
assert.Equal(t, codersdk.InsightsReportIntervalWeek, parsedInterval)
}
})
}
}
func TestLastReportIntervalHasAtLeastSixDays(t *testing.T) {
t.Parallel()
loc, err := time.LoadLocation("Europe/Warsaw")
require.NoError(t, err)
testCases := []struct {
name string
startTime time.Time
endTime time.Time
expected bool
}{
{
name: "perfectly full week",
startTime: time.Date(2023, time.September, 11, 12, 0, 0, 0, loc),
endTime: time.Date(2023, time.September, 18, 12, 0, 0, 0, loc),
expected: true,
},
{
name: "exactly 6 days apart",
startTime: time.Date(2023, time.September, 11, 12, 0, 0, 0, loc),
endTime: time.Date(2023, time.September, 17, 12, 0, 0, 0, loc),
expected: true,
},
{
name: "less than 6 days apart",
startTime: time.Date(2023, time.September, 11, 12, 0, 0, 0, time.UTC),
endTime: time.Date(2023, time.September, 17, 11, 0, 0, 0, time.UTC),
expected: false,
},
{
name: "forward DST change, 5 days and 23 hours apart",
startTime: time.Date(2023, time.March, 22, 12, 0, 0, 0, loc), // A day before DST starts
endTime: time.Date(2023, time.March, 28, 12, 0, 0, 0, loc), // Exactly 6 "days" apart
expected: true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
t.Log("startTime: ", tc.startTime)
t.Log("endTime: ", tc.endTime)
result := lastReportIntervalHasAtLeastSixDays(tc.startTime, tc.endTime)
if result != tc.expected {
t.Errorf("Expected %v, but got %v for start time %v and end time %v", tc.expected, result, tc.startTime, tc.endTime)
}
})
}
}
// stripTime strips the time from a time.Time value, but keeps the date and TZ.
func stripTime(t time.Time) time.Time {
y, m, d := t.Date()
return time.Date(y, m, d, 0, 0, 0, 0, t.Location())
}