diff --git a/DisciplineSemester.go b/DisciplineSemester.go new file mode 100644 index 0000000..52d92d2 --- /dev/null +++ b/DisciplineSemester.go @@ -0,0 +1,17 @@ +package main + +type DisciplineSemester struct { + Semester int + DisciplineId int +} + +type DisciplineSemesters []DisciplineSemester + +func (disciplines DisciplineSemesters) Has(disciplineId int) bool { + for _, discipline := range disciplines { + if discipline.DisciplineId == disciplineId { + return true + } + } + return false +} diff --git a/Storage.go b/Storage.go index 5d65d99..1c73d7d 100644 --- a/Storage.go +++ b/Storage.go @@ -29,27 +29,29 @@ type Storage struct { const IsAbsentScoreValue = float32(-999999) const DisciplineAmountThresholdForSemesterSwitch = 2 -func (storage *Storage) getDisciplineScoreResultsByStudentId(studentId int) (scoreApi.DisciplineScoreResults, error) { - semester, disciplineIds, err := storage.getStudentDisciplinesIdsForLastSemester(studentId) +const MaxSemesterUpdatedInterval = time.Hour * 24 * 7 * 6 // 6 weeks +// 6 weeks = 2 weeks fir winter holidays + 3 weeks for exams + 2 weeks for next semester lectures +func (storage *Storage) getDisciplineScoreResultsByStudentId(studentId int) (scoreApi.DisciplineScoreResults, error) { + disciplines, err := storage.getActualStudentDisciplines(studentId) if err != nil { return nil, err } - disciplineScoreResults := make([]scoreApi.DisciplineScoreResult, len(disciplineIds)) + disciplineScoreResults := make([]scoreApi.DisciplineScoreResult, len(disciplines)) wg := sync.WaitGroup{} - wg.Add(len(disciplineIds)) + wg.Add(len(disciplines)) - for _index := range disciplineIds { + for _index := range disciplines { go func(index int) { - disciplineId := disciplineIds[index] + disciplineId := disciplines[index].DisciplineId disciplineScoreResults[index] = scoreApi.DisciplineScoreResult{ Discipline: scoreApi.Discipline{ Id: disciplineId, Name: storage.getDisciplineName(disciplineId), }, - ScoreRating: storage.scoreRatingLoader.load(storage.year, semester, disciplineId, studentId), + ScoreRating: storage.scoreRatingLoader.load(storage.year, disciplines[index].Semester, disciplineId, studentId), } wg.Done() }(_index) @@ -61,7 +63,7 @@ func (storage *Storage) getDisciplineScoreResultsByStudentId(studentId int) (sco } func (storage *Storage) getDisciplineScoreResultByStudentId(studentId int, disciplineId int) (scoreApi.DisciplineScoreResult, error) { - semester, err := storage.getSemesterByStudentIdAndDisciplineId(studentId, disciplineId) + semester, err := storage.getSemesterByDisciplineId(disciplineId) if err != nil { return scoreApi.DisciplineScoreResult{}, err @@ -82,7 +84,7 @@ func (storage *Storage) getDisciplineScoreResultByStudentId(studentId int, disci } func (storage *Storage) getDisciplineScore(studentId int, disciplineId int, lessonId int) (scoreApi.DisciplineScore, error) { - semester, err := storage.getSemesterByStudentIdAndDisciplineId(studentId, disciplineId) + semester, err := storage.getSemesterByDisciplineId(disciplineId) if err != nil { return scoreApi.DisciplineScore{}, err @@ -101,45 +103,95 @@ func (storage *Storage) getDisciplineScore(studentId int, disciplineId int, less }, nil } -func (storage *Storage) getStudentDisciplinesIdsForLastSemester(studentId int) (int, []int, error) { - var semester int - var stringIds []string - var err error - - stringIds = make([]string, 0) - for semester = 2; semester >= 1; semester-- { - studentDisciplinesKey := fmt.Sprintf("%d:%d:student_disciplines:%d", storage.year, semester, studentId) - stringIds, err = storage.redis.SMembers(context.Background(), studentDisciplinesKey).Result() - if err != nil && !errors.Is(err, redis.Nil) { - return 0, nil, err +// getActualStudentDisciplines +// 1. Get student disciplines for the first semester +// 2. Get student disciplines for the second semester +// 3. If the second semester disciplines is empty, return the first semester disciplines +// 4. Check disciplines from the first semester - if they are not in the second semester, check the last update time +// 5. If the last update time is less than 6 weeks, add the discipline to the result +// 6. Result will contain disciplines from the seconds semester + from first semesters that are not in the second semester and have been updated less than 6 weeks ago +func (storage *Storage) getActualStudentDisciplines(studentId int) ([]DisciplineSemester, error) { + firstSemesterDisciplines, err := storage.getStudentDisciplinesIdsForSemester(studentId, 1) + if err != nil { + return nil, err + } + + secondSemesterDisciplines, err := storage.getStudentDisciplinesIdsForSemester(studentId, 2) + if err != nil { + return nil, err + } + + if len(secondSemesterDisciplines) == 0 { + return firstSemesterDisciplines, nil + } + + cleanedFirstSemesterDisciplines := make([]DisciplineSemester, 0, len(firstSemesterDisciplines)) + + var lastUpdatedAt time.Time + for _, firstSemesterDiscipline := range firstSemesterDisciplines { + if secondSemesterDisciplines.Has(firstSemesterDiscipline.DisciplineId) { + continue + } + + _, lastUpdatedAt, err = storage.getDisciplineSemesterAndUpdatedAt(firstSemesterDiscipline.DisciplineId) + if err != nil { + return nil, err } - if err == nil && len(stringIds) >= 2 { - break + if time.Since(lastUpdatedAt) < MaxSemesterUpdatedInterval { + cleanedFirstSemesterDisciplines = append(cleanedFirstSemesterDisciplines, firstSemesterDiscipline) } } - ids := make([]int, len(stringIds)) + if len(cleanedFirstSemesterDisciplines) == 0 { + return secondSemesterDisciplines, nil + } + + disciplines := make([]DisciplineSemester, len(cleanedFirstSemesterDisciplines)+len(secondSemesterDisciplines)) + copy(disciplines, cleanedFirstSemesterDisciplines) + copy(disciplines[len(cleanedFirstSemesterDisciplines):], secondSemesterDisciplines) + + return disciplines, nil +} + +func (storage *Storage) getStudentDisciplinesIdsForSemester(studentId int, semester int) (DisciplineSemesters, error) { + studentDisciplinesKey := fmt.Sprintf("%d:%d:student_disciplines:%d", storage.year, semester, studentId) + stringIds, err := storage.redis.SMembers(context.Background(), studentDisciplinesKey).Result() + if err != nil && !errors.Is(err, redis.Nil) { + return nil, err + } + + disciplineSemesters := make(DisciplineSemesters, len(stringIds)) for index, stringId := range stringIds { - ids[index], _ = strconv.Atoi(stringId) + disciplineSemesters[index].Semester = semester + disciplineSemesters[index].DisciplineId, _ = strconv.Atoi(stringId) } - return semester, ids, nil + + return disciplineSemesters, nil } -func (storage *Storage) getSemesterByStudentIdAndDisciplineId(studentId int, disciplineId int) (int, error) { - for semester := 2; semester >= 1; semester-- { - studentDisciplinesKey := fmt.Sprintf("%d:%d:student_disciplines:%d", storage.year, semester, studentId) +func (storage *Storage) getSemesterByDisciplineId(disciplineId int) (int, error) { + semester, _, err := storage.getDisciplineSemesterAndUpdatedAt(disciplineId) + return semester, err +} - isMember, err := storage.redis.SIsMember(context.Background(), studentDisciplinesKey, disciplineId).Result() +func (storage *Storage) getDisciplineSemesterAndUpdatedAt(disciplineId int) (semester int, updatedAt time.Time, err error) { + disciplineLastUpdateAtKey := fmt.Sprintf("%d:discipline_semester_updated_at:%d", storage.year, disciplineId) + disciplineLastUpdateAtValue, err := storage.redis.Get(context.Background(), disciplineLastUpdateAtKey).Result() + if err != nil && !errors.Is(err, redis.Nil) { + return 0, time.Time{}, err + } - if err != nil && !errors.Is(err, redis.Nil) { - return 0, err - } else if isMember { - return semester, nil - } + if len(disciplineLastUpdateAtValue) < 2 { + return 0, time.Time{}, nil } - return 0, nil + semester, _ = strconv.Atoi(disciplineLastUpdateAtValue[0:1]) + unixTimestamp, _ := strconv.ParseInt(disciplineLastUpdateAtValue[1:], 10, 0) + + updatedAt = time.Unix(unixTimestamp, 0) + + return semester, updatedAt, nil } func (storage *Storage) getScores(semester int, disciplineId int, studentId int) []scoreApi.Score { diff --git a/Storage_test.go b/Storage_test.go index 17e6c7c..275b15b 100644 --- a/Storage_test.go +++ b/Storage_test.go @@ -60,7 +60,11 @@ func TestNewStorage(t *testing.T) { } func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { - t.Run("success", func(t *testing.T) { + t.Parallel() + + t.Run("success-mixed-semester", func(t *testing.T) { + t.Parallel() + lessonTypes := GetTestLessonTypes() expectedResults := scoreApi.DisciplineScoreResults{ @@ -79,7 +83,7 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { }, scoreApi.DisciplineScoreResult{ Discipline: scoreApi.Discipline{ - Id: 110, + Id: 200, Name: "Гроші та лихварство", }, ScoreRating: scoreApi.ScoreRating{ @@ -98,20 +102,32 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1100" studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1100" - redisMock.ExpectSMembers(studentDisciplinesKeySemester2).SetVal([]string{ - "200", - }) redisMock.ExpectSMembers(studentDisciplinesKeySemester1).SetVal([]string{ "100", "110", + "200", // emualte that discipline was in both semester + }) + redisMock.ExpectSMembers(studentDisciplinesKeySemester2).SetVal([]string{ + "200", }) + oldDate := time.Now().Add(-MaxSemesterUpdatedInterval - time.Hour) + + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:100" + discipline2SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:110" + + discipline1SemesterUpdatedAtValue := "1" + strconv.FormatInt(time.Now().Unix(), 10) + discipline2SemesterUpdatedAtValue := "1" + strconv.FormatInt(oldDate.Unix(), 10) + + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetVal(discipline1SemesterUpdatedAtValue) + redisMock.ExpectGet(discipline2SemesterUpdatedAtKey).SetVal(discipline2SemesterUpdatedAtValue) + redisMock.ExpectHGet("2026:discipline:100", "name").SetVal(expectedResults[0].Discipline.Name) - redisMock.ExpectHGet("2026:discipline:110", "name").SetVal(expectedResults[1].Discipline.Name) + redisMock.ExpectHGet("2026:discipline:200", "name").SetVal(expectedResults[1].Discipline.Name) scoreRatingLoader := NewMockScoreRatingLoaderInterface(t) scoreRatingLoader.On("load", 2026, 1, 100, 1100).Return(expectedResults[0].ScoreRating) - scoreRatingLoader.On("load", 2026, 1, 110, 1100).Return(expectedResults[1].ScoreRating) + scoreRatingLoader.On("load", 2026, 2, 200, 1100).Return(expectedResults[1].ScoreRating) storage := Storage{ redis: redisClient, @@ -128,6 +144,8 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { }) t.Run("success_second_semester", func(t *testing.T) { + t.Parallel() + lessonTypes := GetTestLessonTypes() expectedResults := scoreApi.DisciplineScoreResults{ @@ -177,6 +195,7 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { redisMock.MatchExpectationsInOrder(false) studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1100" + studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1100" redisMock.ExpectSMembers(studentDisciplinesKeySemester2).SetVal([]string{ "200", @@ -184,6 +203,8 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { "210", }) + redisMock.ExpectSMembers(studentDisciplinesKeySemester1).RedisNil() + redisMock.ExpectHGet("2026:discipline:200", "name").SetVal(expectedResults[0].Discipline.Name) redisMock.ExpectHGet("2026:discipline:204", "name").SetVal(expectedResults[1].Discipline.Name) redisMock.ExpectHGet("2026:discipline:210", "name").SetVal(expectedResults[2].Discipline.Name) @@ -216,8 +237,8 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1100" studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1100" - redisMock.ExpectSMembers(studentDisciplinesKeySemester2).RedisNil() redisMock.ExpectSMembers(studentDisciplinesKeySemester1).RedisNil() + redisMock.ExpectSMembers(studentDisciplinesKeySemester2).RedisNil() storage := Storage{ redis: redisClient, @@ -233,7 +254,7 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { assert.NoError(t, redisMock.ExpectationsWereMet()) }) - t.Run("redis_error", func(t *testing.T) { + t.Run("redis_error_first_semester", func(t *testing.T) { lessonTypes := GetTestLessonTypes() expectedError := errors.New("expected error") @@ -242,9 +263,7 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { redisMock.MatchExpectationsInOrder(true) studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1100" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1100" - redisMock.ExpectSMembers(studentDisciplinesKeySemester2).RedisNil() redisMock.ExpectSMembers(studentDisciplinesKeySemester1).SetErr(expectedError) storage := Storage{ @@ -263,6 +282,79 @@ func TestStorageGetDisciplineScoreResultsByStudentId(t *testing.T) { assert.NoError(t, redisMock.ExpectationsWereMet()) }) + t.Run("redis_error_second_semester", func(t *testing.T) { + lessonTypes := GetTestLessonTypes() + + expectedError := errors.New("expected error") + + redisClient, redisMock := redismock.NewClientMock() + redisMock.MatchExpectationsInOrder(true) + + studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1100" + studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1100" + + redisMock.ExpectSMembers(studentDisciplinesKeySemester1).RedisNil() + redisMock.ExpectSMembers(studentDisciplinesKeySemester2).SetErr(expectedError) + + storage := Storage{ + redis: redisClient, + year: 2026, + lessonTypes: lessonTypes, + scoreRatingLoader: NewMockScoreRatingLoaderInterface(t), + } + + actualResults, err := storage.getDisciplineScoreResultsByStudentId(1100) + + assert.Nil(t, actualResults) + assert.Error(t, err) + assert.Equal(t, expectedError, err) + + assert.NoError(t, redisMock.ExpectationsWereMet()) + }) + + t.Run("redis_error_get_discipline_updated_at", func(t *testing.T) { + t.Parallel() + + lessonTypes := GetTestLessonTypes() + + redisClient, redisMock := redismock.NewClientMock() + redisMock.MatchExpectationsInOrder(false) + + studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1100" + studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1100" + + redisMock.ExpectSMembers(studentDisciplinesKeySemester1).SetVal([]string{ + "100", + "110", + "200", // emualte that discipline was in both semester + }) + redisMock.ExpectSMembers(studentDisciplinesKeySemester2).SetVal([]string{ + "200", + }) + + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:100" + discipline2SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:110" + + discipline1SemesterUpdatedAtValue := "1" + strconv.FormatInt(time.Now().Unix(), 10) + + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetVal(discipline1SemesterUpdatedAtValue) + redisMock.ExpectGet(discipline2SemesterUpdatedAtKey).SetErr(assert.AnError) + + storage := Storage{ + redis: redisClient, + year: 2026, + lessonTypes: lessonTypes, + } + + actualResults, err := storage.getDisciplineScoreResultsByStudentId(1100) + + assert.Nil(t, actualResults) + assert.Error(t, err) + assert.Equal(t, assert.AnError, err) + + assert.NoError(t, redisMock.ExpectationsWereMet()) + }) + } func TestGetDisciplineScoreResultByStudentId(t *testing.T) { @@ -315,11 +407,9 @@ func TestGetDisciplineScoreResultByStudentId(t *testing.T) { redisClient, redisMock := redismock.NewClientMock() redisMock.MatchExpectationsInOrder(true) - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, expectedResult.Discipline.Id).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, expectedResult.Discipline.Id).SetVal(true) + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:199" + discipline1SemesterUpdatedAtValue := "1" + strconv.FormatInt(time.Now().Unix(), 10) + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetVal(discipline1SemesterUpdatedAtValue) redisMock.ExpectHGet("2026:discipline:199", "name").SetVal(expectedResult.Discipline.Name) @@ -377,11 +467,9 @@ func TestGetDisciplineScoreResultByStudentId(t *testing.T) { redisClient, redisMock := redismock.NewClientMock() redisMock.MatchExpectationsInOrder(true) - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, expectedResult.Discipline.Id).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, expectedResult.Discipline.Id).SetVal(true) + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:199" + discipline1SemesterUpdatedAtValue := "1" + strconv.FormatInt(time.Now().Unix(), 10) + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetVal(discipline1SemesterUpdatedAtValue) redisMock.ExpectHGet("2026:discipline:199", "name").SetVal(expectedResult.Discipline.Name) @@ -405,7 +493,7 @@ func TestGetDisciplineScoreResultByStudentId(t *testing.T) { assert.NoError(t, redisMock.ExpectationsWereMet()) }) - t.Run("student_has_not_discipline", func(t *testing.T) { + t.Run("discipline_never_updated", func(t *testing.T) { lessonTypes := GetTestLessonTypes() redisClient, redisMock := redismock.NewClientMock() @@ -413,11 +501,8 @@ func TestGetDisciplineScoreResultByStudentId(t *testing.T) { disciplineId := 850 - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, disciplineId).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, disciplineId).RedisNil() + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:850" + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).RedisNil() storage := Storage{ redis: redisClient, @@ -443,11 +528,8 @@ func TestGetDisciplineScoreResultByStudentId(t *testing.T) { disciplineId := 850 - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, disciplineId).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, disciplineId).SetErr(expectedError) + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:850" + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetErr(expectedError) storage := Storage{ redis: redisClient, @@ -491,11 +573,9 @@ func TestGetDisciplineScore(t *testing.T) { redisClient, redisMock := redismock.NewClientMock() redisMock.MatchExpectationsInOrder(true) - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, expectedResult.Discipline.Id).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, expectedResult.Discipline.Id).SetVal(true) + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:199" + discipline1SemesterUpdatedAtValue := "1" + strconv.FormatInt(time.Now().Unix(), 10) + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetVal(discipline1SemesterUpdatedAtValue) redisMock.ExpectHGet("2026:discipline:199", "name").SetVal(expectedResult.Discipline.Name) @@ -547,11 +627,9 @@ func TestGetDisciplineScore(t *testing.T) { redisClient, redisMock := redismock.NewClientMock() redisMock.MatchExpectationsInOrder(true) - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, expectedResult.Discipline.Id).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, expectedResult.Discipline.Id).SetVal(true) + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:199" + discipline1SemesterUpdatedAtValue := "1" + strconv.FormatInt(time.Now().Unix(), 10) + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetVal(discipline1SemesterUpdatedAtValue) redisMock.ExpectHGet("2026:discipline:199", "name").SetVal(expectedResult.Discipline.Name) @@ -594,11 +672,9 @@ func TestGetDisciplineScore(t *testing.T) { redisClient, redisMock := redismock.NewClientMock() redisMock.MatchExpectationsInOrder(true) - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, expectedResult.Discipline.Id).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, expectedResult.Discipline.Id).SetVal(true) + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:199" + discipline1SemesterUpdatedAtValue := "1" + strconv.FormatInt(time.Now().Unix(), 10) + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetVal(discipline1SemesterUpdatedAtValue) redisMock.ExpectHGet("2026:discipline:199", "name").SetVal(expectedResult.Discipline.Name) @@ -643,11 +719,9 @@ func TestGetDisciplineScore(t *testing.T) { redisClient, redisMock := redismock.NewClientMock() redisMock.MatchExpectationsInOrder(true) - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, expectedResult.Discipline.Id).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, expectedResult.Discipline.Id).SetVal(true) + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:199" + discipline1SemesterUpdatedAtValue := "1" + strconv.FormatInt(time.Now().Unix(), 10) + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetVal(discipline1SemesterUpdatedAtValue) redisMock.ExpectHGet("2026:discipline:199", "name").SetVal(expectedResult.Discipline.Name) @@ -676,7 +750,7 @@ func TestGetDisciplineScore(t *testing.T) { assert.NoError(t, redisMock.ExpectationsWereMet()) }) - t.Run("student_has_not_discipline", func(t *testing.T) { + t.Run("discipline_never_updated", func(t *testing.T) { lessonTypes := GetTestLessonTypes() expectedResult := scoreApi.DisciplineScore{ @@ -690,11 +764,8 @@ func TestGetDisciplineScore(t *testing.T) { redisClient, redisMock := redismock.NewClientMock() redisMock.MatchExpectationsInOrder(true) - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, expectedResult.Discipline.Id).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, expectedResult.Discipline.Id).RedisNil() + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:199" + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).RedisNil() storage := Storage{ redis: redisClient, @@ -721,11 +792,8 @@ func TestGetDisciplineScore(t *testing.T) { disciplineId := 850 - studentDisciplinesKeySemester1 := "2026:1:student_disciplines:1200" - studentDisciplinesKeySemester2 := "2026:2:student_disciplines:1200" - - redisMock.ExpectSIsMember(studentDisciplinesKeySemester2, disciplineId).RedisNil() - redisMock.ExpectSIsMember(studentDisciplinesKeySemester1, disciplineId).SetErr(expectedError) + discipline1SemesterUpdatedAtKey := "2026:discipline_semester_updated_at:850" + redisMock.ExpectGet(discipline1SemesterUpdatedAtKey).SetErr(expectedError) storage := Storage{ redis: redisClient,