From 1ddee2d4673ba484140da64cc5262c3d623a6dd9 Mon Sep 17 00:00:00 2001 From: HermanPlay Date: Mon, 10 Aug 2026 21:26:12 +0200 Subject: [PATCH 1/2] feat: expose test-result file URLs, configurable cookie Secure flag, fix config drift - TestResult schema gains stdoutUrl/stderrUrl/diffUrl signed URLs (submission detail preloads file relations) - refresh-token cookie Secure flag now env-driven (COOKIE_SECURE), default false - fix FILE_STORAGE_PORT doc drift (.env.example used public 8888) - correct APP_PORT doc comment; document FILE_STORAGE_PUBLIC_URL/COOKIE_SECURE --- .env.example | 4 +- internal/api/http/routes/auth.go | 24 +++++++--- internal/api/http/routes/auth_test.go | 53 +++++++++++++++++++-- internal/config/config.go | 20 ++++++-- internal/initialization/initialization.go | 2 +- package/domain/schemas/submission.go | 3 ++ package/repository/submission_repository.go | 3 ++ package/service/submission_service.go | 16 +++++++ package/service/submission_service_test.go | 48 +++++++++++++++++++ 9 files changed, 156 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index 3f25d053..51dce928 100644 --- a/.env.example +++ b/.env.example @@ -8,9 +8,9 @@ DB_NAME=maxit # Application Configuration APP_PORT=8000 -# File Storage Configuration (using localhost) +# File Storage Configuration (internal server, NOT the public signed-URL port) FILE_STORAGE_HOST=localhost -FILE_STORAGE_PORT=8888 +FILE_STORAGE_PORT=8081 # Queue Configuration (RabbitMQ - using localhost) QUEUE_NAME=worker_queue diff --git a/internal/api/http/routes/auth.go b/internal/api/http/routes/auth.go index 39c42143..e1dfe3bd 100644 --- a/internal/api/http/routes/auth.go +++ b/internal/api/http/routes/auth.go @@ -28,13 +28,17 @@ func newAuthResponse(tokens *schemas.JWTTokens) AuthResponse { } // setRefreshTokenCookie sets the refresh token as an httpOnly cookie -func setRefreshTokenCookie(w http.ResponseWriter, path, refreshToken string) { +func setRefreshTokenCookie(w http.ResponseWriter, path, refreshToken string, secure *bool) { + secureFlag := false + if secure != nil { + secureFlag = *secure + } http.SetCookie(w, &http.Cookie{ Name: "refresh_token", Value: refreshToken, Path: path, HttpOnly: true, - Secure: false, // Set to true in production with HTTPS + Secure: secureFlag, SameSite: http.SameSiteStrictMode, MaxAge: 7 * 24 * 60 * 60, // 7 days }) @@ -49,6 +53,7 @@ type AuthRoute interface { type AuthRouteImpl struct { refreshTokenPath string + cookieSecure *bool userService service.UserService authService service.AuthService logger *zap.SugaredLogger @@ -88,7 +93,7 @@ func (ar *AuthRouteImpl) Login(w http.ResponseWriter, r *http.Request) { return } - setRefreshTokenCookie(w, ar.refreshTokenPath, tokens.RefreshToken) + setRefreshTokenCookie(w, ar.refreshTokenPath, tokens.RefreshToken, ar.cookieSecure) authResponse := newAuthResponse(tokens) @@ -130,7 +135,7 @@ func (ar *AuthRouteImpl) Register(w http.ResponseWriter, r *http.Request) { return } - setRefreshTokenCookie(w, ar.refreshTokenPath, tokens.RefreshToken) + setRefreshTokenCookie(w, ar.refreshTokenPath, tokens.RefreshToken, ar.cookieSecure) authResponse := newAuthResponse(tokens) @@ -173,7 +178,7 @@ func (ar *AuthRouteImpl) RefreshToken(w http.ResponseWriter, r *http.Request) { return } - setRefreshTokenCookie(w, ar.refreshTokenPath, tokens.RefreshToken) + setRefreshTokenCookie(w, ar.refreshTokenPath, tokens.RefreshToken, ar.cookieSecure) authResponse := newAuthResponse(tokens) @@ -196,21 +201,26 @@ func (ar *AuthRouteImpl) Logout(w http.ResponseWriter, r *http.Request) { } // Clear the refresh token cookie + secureFlag := false + if ar.cookieSecure != nil { + secureFlag = *ar.cookieSecure + } http.SetCookie(w, &http.Cookie{ Name: "refresh_token", Path: ar.refreshTokenPath, Value: "", MaxAge: -1, HttpOnly: true, - Secure: false, // Set to true in production with HTTPS + Secure: secureFlag, }) httputils.ReturnSuccess(w, http.StatusOK, httputils.NewMessageResponse("Logged out successfully")) } -func NewAuthRoute(userService service.UserService, authService service.AuthService, refreshTokenPath string) AuthRoute { +func NewAuthRoute(userService service.UserService, authService service.AuthService, refreshTokenPath string, cookieSecure bool) AuthRoute { route := &AuthRouteImpl{ refreshTokenPath: refreshTokenPath, + cookieSecure: &cookieSecure, userService: userService, authService: authService, logger: utils.NewNamedLogger("auth"), diff --git a/internal/api/http/routes/auth_test.go b/internal/api/http/routes/auth_test.go index 17284b43..8d5a51a3 100644 --- a/internal/api/http/routes/auth_test.go +++ b/internal/api/http/routes/auth_test.go @@ -30,7 +30,7 @@ func TestLogin(t *testing.T) { us := mock_service.NewMockUserService(ctrl) as := mock_service.NewMockAuthService(ctrl) - route := routes.NewAuthRoute(us, as, "/auth/refresh") + route := routes.NewAuthRoute(us, as, "/auth/refresh", false) db := &testutils.MockDatabase{} handler := httputils.MockDatabaseMiddleware(http.HandlerFunc(route.Login), db) server := httptest.NewServer(handler) @@ -234,7 +234,7 @@ func TestRegister(t *testing.T) { us := mock_service.NewMockUserService(ctrl) as := mock_service.NewMockAuthService(ctrl) - route := routes.NewAuthRoute(us, as, "/auth/refresh") + route := routes.NewAuthRoute(us, as, "/auth/refresh", false) db := &testutils.MockDatabase{} handler := httputils.MockDatabaseMiddleware(http.HandlerFunc(route.Register), db) server := httptest.NewServer(handler) @@ -394,7 +394,7 @@ func TestRefreshToken(t *testing.T) { us := mock_service.NewMockUserService(ctrl) as := mock_service.NewMockAuthService(ctrl) - route := routes.NewAuthRoute(us, as, "/auth/refresh") + route := routes.NewAuthRoute(us, as, "/auth/refresh", false) db := &testutils.MockDatabase{} handler := httputils.MockDatabaseMiddleware(http.HandlerFunc(route.RefreshToken), db) server := httptest.NewServer(handler) @@ -553,3 +553,50 @@ func TestRefreshToken(t *testing.T) { assert.Equal(t, "new_refresh_token", refreshTokenCookie.Value) }) } + +func TestRefreshTokenCookieSecureFlag(t *testing.T) { + testCases := []struct { + name string + cookieSecure bool + expectedValue bool + }{ + {"Secure enabled", true, true}, + {"Secure disabled", false, false}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + us := mock_service.NewMockUserService(ctrl) + as := mock_service.NewMockAuthService(ctrl) + route := routes.NewAuthRoute(us, as, "/auth/refresh", tc.cookieSecure) + + tokens := &schemas.JWTTokens{AccessToken: "access", RefreshToken: "refresh"} + as.EXPECT().Login(gomock.Any(), gomock.Any()).Return(tokens, nil) + + db := &testutils.MockDatabase{} + handler := httputils.MockDatabaseMiddleware(http.HandlerFunc(route.Login), db) + server := httptest.NewServer(handler) + defer server.Close() + + resp, err := http.Post(server.URL, "application/json", strings.NewReader(`{"email":"a@b.c","password":"password"}`)) + require.NoError(t, err) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + cookies := resp.Cookies() + var refreshTokenCookie *http.Cookie + for _, cookie := range cookies { + if cookie.Name == refreshTokenCookieName { + refreshTokenCookie = cookie + break + } + } + require.NotNil(t, refreshTokenCookie) + assert.Equal(t, tc.expectedValue, refreshTokenCookie.Secure, "cookie Secure flag") + }) + } +} diff --git a/internal/config/config.go b/internal/config/config.go index c853114a..23c95062 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -33,6 +33,8 @@ type APIConfig struct { Port uint16 RefreshTokenPath string AccessTokenMinutes uint16 + // CookieSecure sets the Secure flag on the refresh-token cookie. Must be true in production (HTTPS). + CookieSecure bool } type CORSConfig struct { @@ -65,6 +67,7 @@ const ( defaultCORSAllowedOrigins = "http://localhost:3000,http://localhost:5173" defaultAccessTokenMinutesStr = "180" defaultSignedURLTTLSecondsStr = "300" // 5 minutes + trueValue = "true" ) // NewConfig creates new Config instance @@ -81,9 +84,15 @@ const ( // // - DB_NAME - database name. Required // -// - API_PORT - application port. Default is 8080 +// - APP_PORT - application port. Default is 8080 // -// - FILE_STORAGE_HOST - file storage host. Required +// - FILE_STORAGE_HOST - file storage internal host. Required +// +// - FILE_STORAGE_PORT - file storage internal port. Required +// +// - FILE_STORAGE_PUBLIC_URL - public base URL for signed file downloads (e.g. https://host/files). Falls back to internal URL if unset (not reachable by browsers) +// +// - COOKIE_SECURE - set to "true" to set the Secure flag on the refresh-token cookie. Required in production (HTTPS) // // - QUEUE_NAME - queue name for sending tasks. Default is "worker_queue" // @@ -154,6 +163,8 @@ func NewConfig() *Config { } accessTokenMinutes := uint16(accessTokenMinutesParsed) + cookieSecure := os.Getenv("COOKIE_SECURE") == trueValue + fileStorageHost := os.Getenv("FILE_STORAGE_HOST") if fileStorageHost == "" { log.Panic("FILE_STORAGE_HOST is not set") @@ -207,14 +218,14 @@ func NewConfig() *Config { } dumpStr := os.Getenv("DUMP") - dump := dumpStr == "true" + dump := dumpStr == trueValue corsAllowedOrigins := os.Getenv("CORS_ALLOWED_ORIGINS") if corsAllowedOrigins == "" { log.Warnf("CORS_ALLOWED_ORIGINS is not set. Using default value %s", defaultCORSAllowedOrigins) corsAllowedOrigins = defaultCORSAllowedOrigins } - corsAllowCredentials := os.Getenv("CORS_ALLOW_CREDENTIALS") == "true" + corsAllowCredentials := os.Getenv("CORS_ALLOW_CREDENTIALS") == trueValue if corsAllowCredentials && corsAllowedOrigins == "*" { log.Panicf(`CORS_ALLOWED_ORIGINS=* and CORS_ALLOW_CREDENTIALS=true cannot be set at the same time. @@ -244,6 +255,7 @@ More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNot Port: appPort, RefreshTokenPath: refreshTokenPath, AccessTokenMinutes: accessTokenMinutes, + CookieSecure: cookieSecure, }, Broker: BrokerConfig{ QueueName: queueName, diff --git a/internal/initialization/initialization.go b/internal/initialization/initialization.go index 9dae0dcc..e355418c 100644 --- a/internal/initialization/initialization.go +++ b/internal/initialization/initialization.go @@ -141,7 +141,7 @@ func NewInitialization(cfg *config.Config) *Initialization { workerService := service.NewWorkerService(queueService, submissionRepository, db) // Routes - authRoute := routes.NewAuthRoute(userService, authService, cfg.API.RefreshTokenPath) + authRoute := routes.NewAuthRoute(userService, authService, cfg.API.RefreshTokenPath, cfg.API.CookieSecure) contestRoute := routes.NewContestRoute(contestService, submissionService) contestManagementRoute := routes.NewContestsManagementRoute(contestService, submissionService) groupRoute := routes.NewGroupRoute(groupService) diff --git a/package/domain/schemas/submission.go b/package/domain/schemas/submission.go index b91e70cd..677035b2 100644 --- a/package/domain/schemas/submission.go +++ b/package/domain/schemas/submission.go @@ -54,6 +54,9 @@ type TestResult struct { ExitCode *int `json:"exitCode"` Code string `json:"code"` ErrorMessage string `json:"errorMessage"` + StdoutURL string `json:"stdoutUrl,omitempty"` + StderrURL string `json:"stderrUrl,omitempty"` + DiffURL string `json:"diffUrl,omitempty"` } // ContestTaskStats contains aggregated statistics for a task in a contest diff --git a/package/repository/submission_repository.go b/package/repository/submission_repository.go index ffc0b6a3..da5f312d 100644 --- a/package/repository/submission_repository.go +++ b/package/repository/submission_repository.go @@ -159,6 +159,9 @@ func (us *submissionRepository) Get(db database.Database, submissionID int64) (* Preload("File"). Preload("Contest"). Preload("Result.TestResults"). + Preload("Result.TestResults.StdoutFile"). + Preload("Result.TestResults.StderrFile"). + Preload("Result.TestResults.DiffFile"). First(&submission).Error if err != nil { return nil, err diff --git a/package/service/submission_service.go b/package/service/submission_service.go index 0a1e0849..de58f96c 100644 --- a/package/service/submission_service.go +++ b/package/service/submission_service.go @@ -817,11 +817,27 @@ func (ss *submissionService) testResultsModelToSchema(testResults []models.TestR Passed: testResult.Passed, Code: testResult.StatusCode.String(), ErrorMessage: testResult.ErrorMessage, + StdoutURL: ss.signedFileURL(testResult.StdoutFile.Path), + StderrURL: ss.signedFileURL(testResult.StderrFile.Path), + DiffURL: ss.signedFileURL(testResult.DiffFile.Path), }) } return result } +// signedFileURL returns a signed URL for the given storage path, or an empty string if signing fails or path is empty. +func (ss *submissionService) signedFileURL(path string) string { + if path == "" || ss.filestorage == nil { + return "" + } + signed, err := ss.filestorage.GetSignedFileURL(path, 0) + if err != nil { + ss.logger.Errorw("Failed to sign file URL", "path", path, "error", err) + return "" + } + return signed +} + func (ss *submissionService) resultModelToSchema(result *models.SubmissionResult) *schemas.SubmissionResult { if result == nil { return nil diff --git a/package/service/submission_service_test.go b/package/service/submission_service_test.go index 45842b10..fb81aee6 100644 --- a/package/service/submission_service_test.go +++ b/package/service/submission_service_test.go @@ -394,6 +394,54 @@ func TestGetAllSignedURLs(t *testing.T) { } } +func TestGetSignedTestResultURLs(t *testing.T) { + setup := setupSubmissionServiceTest(t) + defer setup.ctrl.Finish() + + stdout := models.File{Path: "solution/1/1/1/stdout/1.out", Bucket: "maxit"} + stderr := models.File{Path: "solution/1/1/1/stderr/1.err", Bucket: "maxit"} + diff := models.File{Path: "solution/1/1/1/diff/1.diff", Bucket: "maxit"} + + submission := &models.Submission{ + ID: 1, + TaskID: 1, + UserID: 1, + Status: types.SubmissionStatusEvaluated, + File: models.File{Path: "solution/1/1/1/solution.py"}, + Result: &models.SubmissionResult{ + ID: 1, SubmissionID: 1, + TestResults: []models.TestResult{ + { + ID: 1, SubmissionResultID: 1, TestCaseID: 1, + StatusCode: types.TestResultStatusCodeOK, + StdoutFile: stdout, + StderrFile: stderr, + DiffFile: diff, + }, + }, + }, + } + + setup.submissionRepository.EXPECT().Get(gomock.Any(), int64(1)).Return(submission, nil).Times(1) + + result, err := setup.service.Get(nil, 1, &schemas.User{Role: "admin"}) + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Result) + require.Len(t, result.Result.TestResults, 1) + + tr := result.Result.TestResults[0] + for name, url := range map[string]string{ + "stdout": tr.StdoutURL, + "stderr": tr.StderrURL, + "diff": tr.DiffURL, + } { + assert.NotEmpty(t, url, "%s URL must be set", name) + assert.Contains(t, url, "expires=", "%s URL must be signed", name) + assert.Contains(t, url, "signature=", "%s URL must be signed", name) + } +} + func TestGet(t *testing.T) { setup := setupSubmissionServiceTest(t) defer setup.ctrl.Finish() From 1f120837aa31786d6681001fcf955753660907d7 Mon Sep 17 00:00:00 2001 From: HermanPlay Date: Mon, 10 Aug 2026 22:46:36 +0200 Subject: [PATCH 2/2] fix: extract env var name constants, resolve goconst for golangci-lint v2.12.2, pin CI version - config.go: define all env var names as constants (envDBHost, envAPPPort, ...), use them in NewConfig and config_test.go - filestorage service/validation_rules: extract context-key and directory constants - test files: replace repeated string literals with constants (goconst) - pin golangci-lint to v2.12.2 in pre-commit + copilot-setup workflows (was @latest; version drift flagged pre-existing goconst issues) --- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/pre-commit.yaml | 2 +- .../api/http/httputils/error_handler_test.go | 15 +- internal/api/http/middleware/cors_test.go | 50 +++--- .../api/http/routes/access_control_test.go | 8 +- internal/api/http/routes/auth_test.go | 28 +-- internal/api/http/routes/consts_test.go | 15 ++ .../http/routes/contests_management_test.go | 41 +++-- internal/api/http/routes/contests_test.go | 16 +- internal/api/http/routes/groups_test.go | 46 ++--- internal/api/http/routes/submissions_test.go | 16 +- .../api/http/routes/tasks_management_test.go | 20 +-- internal/api/http/routes/users_test.go | 71 ++++---- internal/config/config.go | 113 +++++++----- internal/config/config_test.go | 164 +++++++++--------- package/filestorage/errors_test.go | 69 ++++---- package/filestorage/service.go | 74 ++++---- package/filestorage/service_test.go | 12 +- package/filestorage/validation_rules.go | 44 ++--- package/filestorage/validation_rules_test.go | 126 +++++++------- .../service/access_control_service_test.go | 4 +- package/service/auth_service_test.go | 59 ++++--- package/service/contest_service_test.go | 145 ++++++++-------- package/service/group_service_test.go | 42 ++--- package/service/language_service_test.go | 127 +++++++------- package/service/submission_service_test.go | 129 +++++++------- package/service/task_service_test.go | 116 +++++++------ package/service/user_service_test.go | 52 +++--- 28 files changed, 876 insertions(+), 730 deletions(-) create mode 100644 internal/api/http/routes/consts_test.go diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 2adcfb9f..64acdaf4 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -46,7 +46,7 @@ jobs: ${{ runner.os }}-go-tools- - name: Install golangci-lint - run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest + run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 - name: Install swag CLI tool run: go install github.com/mini-maxit/swag/cmd/swag@latest diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 0204a7be..0f18e72d 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -19,7 +19,7 @@ jobs: go-version: stable - name: Install dependency tools - run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest | go install golang.org/x/tools/cmd/goimports@latest | go install github.com/swaggo/swag/cmd/swag@latest + run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 | go install golang.org/x/tools/cmd/goimports@latest | go install github.com/swaggo/swag/cmd/swag@latest - name: Set up pre-commit Cache uses: pre-commit/action@v3.0.1 diff --git a/internal/api/http/httputils/error_handler_test.go b/internal/api/http/httputils/error_handler_test.go index 70c12a05..8c780d44 100644 --- a/internal/api/http/httputils/error_handler_test.go +++ b/internal/api/http/httputils/error_handler_test.go @@ -202,7 +202,7 @@ func TestHttpToErrorCode(t *testing.T) { }{ {http.StatusNotFound, "ERR_NOT_FOUND"}, {http.StatusInternalServerError, "ERR_INTERNAL_SERVER_ERROR"}, - {http.StatusBadRequest, "ERR_BAD_REQUEST"}, + {http.StatusBadRequest, errBadRequest}, {http.StatusNonAuthoritativeInfo, "ERR_NON_AUTHORITATIVE_INFORMATION"}, } for _, tc := range tests { @@ -213,7 +213,10 @@ func TestHttpToErrorCode(t *testing.T) { } } -const applicationJSON = "application/json" +const ( + applicationJSON = "application/json" + errBadRequest = "ERR_BAD_REQUEST" +) func TestReturnError(t *testing.T) { w := httptest.NewRecorder() @@ -233,8 +236,8 @@ func TestReturnError(t *testing.T) { if resp.Ok { t.Fatalf("expected ok=false") } - if resp.Data.Code != "ERR_BAD_REQUEST" { - t.Fatalf("expected code ERR_BAD_REQUEST, got %s", resp.Data.Code) + if resp.Data.Code != errBadRequest { + t.Fatalf("expected code %s, got %s", errBadRequest, resp.Data.Code) } if resp.Data.Message != "bad req" { t.Fatalf("expected message 'bad req', got %s", resp.Data.Message) @@ -348,8 +351,8 @@ func TestHandleValidationError(t *testing.T) { if resp.Ok { t.Fatalf("expected ok=false") } - if resp.Data.Code != "ERR_BAD_REQUEST" { - t.Fatalf("expected code ERR_BAD_REQUEST, got %s", resp.Data.Code) + if resp.Data.Code != errBadRequest { + t.Fatalf("expected code %s, got %s", errBadRequest, resp.Data.Code) } if resp.Data.Message != InvalidRequestBodyMessage { t.Fatalf("expected message %q, got %q", InvalidRequestBodyMessage, resp.Data.Message) diff --git a/internal/api/http/middleware/cors_test.go b/internal/api/http/middleware/cors_test.go index 7f68955b..fe7ca904 100644 --- a/internal/api/http/middleware/cors_test.go +++ b/internal/api/http/middleware/cors_test.go @@ -9,6 +9,14 @@ import ( "github.com/mini-maxit/backend/internal/config" ) +const ( + corsAllowOriginHeader = "Access-Control-Allow-Origin" + corsAllowCredentialsHeader = "Access-Control-Allow-Credentials" + corsLocalOrigin = "http://localhost:3000" + corsAllowedOrigins = "http://localhost:3000,http://localhost:5173" + corsCredentialsTrue = "true" +) + func TestCORSMiddleware(t *testing.T) { tests := []struct { name string @@ -28,28 +36,28 @@ func TestCORSMiddleware(t *testing.T) { }, expectedStatus: http.StatusNoContent, expectedHeaders: map[string]string{ - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH", - "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With", - "Access-Control-Allow-Credentials": "false", - "Access-Control-Max-Age": "86400", + corsAllowOriginHeader: "*", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH", + "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With", + corsAllowCredentialsHeader: "false", + "Access-Control-Max-Age": "86400", }, }, { name: "GET request with specific allowed origin", method: http.MethodGet, - origin: "http://localhost:3000", + origin: corsLocalOrigin, corsConfig: &config.CORSConfig{ - AllowedOrigins: "http://localhost:3000,http://localhost:5173", + AllowedOrigins: corsAllowedOrigins, AllowCredentials: true, }, expectedStatus: http.StatusOK, expectedHeaders: map[string]string{ - "Access-Control-Allow-Origin": "http://localhost:3000", - "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH", - "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With", - "Access-Control-Allow-Credentials": "true", - "Access-Control-Expose-Headers": "Content-Length, Content-Type", + corsAllowOriginHeader: corsLocalOrigin, + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH", + "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With", + corsAllowCredentialsHeader: corsCredentialsTrue, + "Access-Control-Expose-Headers": "Content-Length, Content-Type", }, }, { @@ -57,14 +65,14 @@ func TestCORSMiddleware(t *testing.T) { method: http.MethodPost, origin: "http://localhost:5173", corsConfig: &config.CORSConfig{ - AllowedOrigins: "http://localhost:3000,http://localhost:5173", + AllowedOrigins: corsAllowedOrigins, AllowCredentials: true, }, expectedStatus: http.StatusOK, expectedHeaders: map[string]string{ - "Access-Control-Allow-Origin": "http://localhost:5173", - "Access-Control-Allow-Credentials": "true", - "Access-Control-Expose-Headers": "Content-Length, Content-Type", + corsAllowOriginHeader: "http://localhost:5173", + corsAllowCredentialsHeader: corsCredentialsTrue, + "Access-Control-Expose-Headers": "Content-Length, Content-Type", }, }, { @@ -72,7 +80,7 @@ func TestCORSMiddleware(t *testing.T) { method: http.MethodGet, origin: "http://evil.com", corsConfig: &config.CORSConfig{ - AllowedOrigins: "http://localhost:3000,http://localhost:5173", + AllowedOrigins: corsAllowedOrigins, AllowCredentials: true, }, expectedStatus: http.StatusForbidden, @@ -83,7 +91,7 @@ func TestCORSMiddleware(t *testing.T) { method: http.MethodGet, origin: "", corsConfig: &config.CORSConfig{ - AllowedOrigins: "http://localhost:3000", + AllowedOrigins: corsLocalOrigin, AllowCredentials: true, }, expectedStatus: http.StatusOK, @@ -92,15 +100,15 @@ func TestCORSMiddleware(t *testing.T) { { name: "Origin with whitespace in allowed list", method: http.MethodGet, - origin: "http://localhost:3000", + origin: corsLocalOrigin, corsConfig: &config.CORSConfig{ AllowedOrigins: "http://localhost:3000 , http://localhost:5173", AllowCredentials: true, }, expectedStatus: http.StatusOK, expectedHeaders: map[string]string{ - "Access-Control-Allow-Origin": "http://localhost:3000", - "Access-Control-Allow-Credentials": "true", + corsAllowOriginHeader: corsLocalOrigin, + corsAllowCredentialsHeader: corsCredentialsTrue, }, }, } diff --git a/internal/api/http/routes/access_control_test.go b/internal/api/http/routes/access_control_test.go index 03aba7e4..0ac9ee44 100644 --- a/internal/api/http/routes/access_control_test.go +++ b/internal/api/http/routes/access_control_test.go @@ -30,8 +30,8 @@ func setupAccessControlTest(t *testing.T) (*gomock.Controller, *mock_service.Moc currentUser := schemas.User{ ID: 1, Name: "Test", - Surname: "User", - Email: "test@email.com", + Surname: testSurname, + Email: testUserEmail, Role: types.UserRoleAdmin, } return ctrl, acs, route, db, currentUser @@ -209,7 +209,7 @@ func TestGetContestCollaborators(t *testing.T) { w := httptest.NewRecorder() expectedCollaborators := []schemas.Collaborator{ - {UserID: 1, UserName: "User1", UserEmail: "user1@email.com", Permission: types.PermissionOwner, AddedAt: "2024-01-01T00:00:00Z"}, + {UserID: 1, UserName: testUserName, UserEmail: testUser1Email, Permission: types.PermissionOwner, AddedAt: "2024-01-01T00:00:00Z"}, {UserID: 2, UserName: "User2", UserEmail: "user2@email.com", Permission: types.PermissionEdit, AddedAt: "2024-01-02T00:00:00Z"}, } @@ -491,7 +491,7 @@ func TestGetTaskCollaborators(t *testing.T) { w := httptest.NewRecorder() expectedCollaborators := []schemas.Collaborator{ - {UserID: 1, UserName: "User1", UserEmail: "user1@email.com", Permission: types.PermissionOwner, AddedAt: "2024-01-01T00:00:00Z"}, + {UserID: 1, UserName: testUserName, UserEmail: testUser1Email, Permission: types.PermissionOwner, AddedAt: "2024-01-01T00:00:00Z"}, } acs.EXPECT().GetCollaborators(gomock.Any(), gomock.Any(), gomock.Any(), int64(1)).Return(expectedCollaborators, nil).Times(1) diff --git a/internal/api/http/routes/auth_test.go b/internal/api/http/routes/auth_test.go index 8d5a51a3..675d653f 100644 --- a/internal/api/http/routes/auth_test.go +++ b/internal/api/http/routes/auth_test.go @@ -88,7 +88,7 @@ func TestLogin(t *testing.T) { Password string `json:"password"` }{ Email: "invalid@email.com", - Password: "password", + Password: testPassword, } jsonBody, err := json.Marshal(reqBody) if err != nil { @@ -119,7 +119,7 @@ func TestLogin(t *testing.T) { Email string `json:"email"` Password string `json:"password"` }{ - Email: "email@email.com", + Email: testLoginEmail, Password: "invalid", } jsonBody, err := json.Marshal(reqBody) @@ -149,8 +149,8 @@ func TestLogin(t *testing.T) { Email string `json:"email"` Password string `json:"password"` }{ - Email: "email@email.com", - Password: "password", + Email: testLoginEmail, + Password: testPassword, } jsonBody, err := json.Marshal(body) if err != nil { @@ -181,8 +181,8 @@ func TestLogin(t *testing.T) { Email string `json:"email"` Password string `json:"password"` }{ - Email: "test@email.com", - Password: "password", + Email: testUserEmail, + Password: testPassword, } jsonBody, err := json.Marshal(body) if err != nil { @@ -191,7 +191,7 @@ func TestLogin(t *testing.T) { expectedTokens := &schemas.JWTTokens{ AccessToken: "access_token", - RefreshToken: "refresh_token", + RefreshToken: refreshTokenCookieName, } as.EXPECT().Login(gomock.Any(), gomock.Any()).Return(expectedTokens, nil).Times(1) @@ -243,7 +243,7 @@ func TestRegister(t *testing.T) { correctRequest := schemas.UserRegisterRequest{ Name: "name", Surname: "surname", - Email: "email@email.com", + Email: testLoginEmail, Username: "username", Password: "HardPassowrd123!", ConfirmPassword: "HardPassowrd123!", @@ -346,7 +346,7 @@ func TestRegister(t *testing.T) { t.Run("Success", func(t *testing.T) { expectedTokens := &schemas.JWTTokens{ AccessToken: "access_token", - RefreshToken: "refresh_token", + RefreshToken: refreshTokenCookieName, } as.EXPECT().Register(gomock.Any(), gomock.Any()).Return(expectedTokens, nil).Times(1) @@ -383,7 +383,7 @@ func TestRegister(t *testing.T) { } } assert.NotNil(t, refreshTokenCookie) - assert.Equal(t, "refresh_token", refreshTokenCookie.Value) + assert.Equal(t, refreshTokenCookieName, refreshTokenCookie.Value) }) } @@ -448,7 +448,7 @@ func TestRefreshToken(t *testing.T) { // Add refresh token cookie req.AddCookie(&http.Cookie{ - Name: "refresh_token", + Name: refreshTokenCookieName, Value: "invalid_refresh_token", }) @@ -479,7 +479,7 @@ func TestRefreshToken(t *testing.T) { // Add refresh token cookie req.AddCookie(&http.Cookie{ - Name: "refresh_token", + Name: refreshTokenCookieName, Value: "valid_refresh_token", }) @@ -517,7 +517,7 @@ func TestRefreshToken(t *testing.T) { // Add refresh token cookie req.AddCookie(&http.Cookie{ - Name: "refresh_token", + Name: refreshTokenCookieName, Value: "valid_refresh_token", }) @@ -544,7 +544,7 @@ func TestRefreshToken(t *testing.T) { cookies := resp.Cookies() var refreshTokenCookie *http.Cookie for _, cookie := range cookies { - if cookie.Name == "refresh_token" { + if cookie.Name == refreshTokenCookieName { refreshTokenCookie = cookie break } diff --git a/internal/api/http/routes/consts_test.go b/internal/api/http/routes/consts_test.go new file mode 100644 index 00000000..05fb63be --- /dev/null +++ b/internal/api/http/routes/consts_test.go @@ -0,0 +1,15 @@ +package routes_test + +const ( + testAdmin = "admin" + testTeacher = "teacher" + testStudent = "student" + testSurname = "User" + testUserName = "User1" + testUserEmail = "test@email.com" + testUser1Email = "user1@email.com" + testExampleMail = "test@example.com" + testLoginEmail = "email@email.com" + testPassword = "password" + testGroupName = "Test Group" +) diff --git a/internal/api/http/routes/contests_management_test.go b/internal/api/http/routes/contests_management_test.go index cfe7d9d2..bc3f4db7 100644 --- a/internal/api/http/routes/contests_management_test.go +++ b/internal/api/http/routes/contests_management_test.go @@ -22,6 +22,11 @@ import ( "go.uber.org/mock/gomock" ) +const ( + testContestName = "Test Contest" + testContestDescription = "Test Description" +) + func TestCreateContest(t *testing.T) { ctrl := gomock.NewController(t) cs := mock_service.NewMockContestService(ctrl) @@ -39,8 +44,8 @@ func TestCreateContest(t *testing.T) { // Mock user and add to context mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := r.Context() ctx = context.WithValue(ctx, httputils.UserKey, mockUser) @@ -96,8 +101,8 @@ func TestCreateContest(t *testing.T) { t.Run("Not authorized", func(t *testing.T) { body := schemas.CreateContest{ - Name: "Test Contest", - Description: "Test Description", + Name: testContestName, + Description: testContestDescription, StartAt: time.Now().Add(1 * time.Hour), } jsonBody, err := json.Marshal(body) @@ -118,8 +123,8 @@ func TestCreateContest(t *testing.T) { t.Run("Success", func(t *testing.T) { body := schemas.CreateContest{ - Name: "Test Contest", - Description: "Test Description", + Name: testContestName, + Description: testContestDescription, StartAt: time.Now().Add(1 * time.Hour), } jsonBody, err := json.Marshal(body) @@ -157,8 +162,8 @@ func TestEditContest(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -273,7 +278,7 @@ func TestEditContest(t *testing.T) { ID: 1, Name: "Updated Contest", CreatedBy: 1, - Description: "Test Description", + Description: testContestDescription, }, IsSubmissionOpen: isSubmissionOpen, }, @@ -316,8 +321,8 @@ func TestDeleteContest(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -409,8 +414,8 @@ func TestGetRegistrationRequests(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -471,7 +476,7 @@ func TestGetRegistrationRequests(t *testing.T) { Surname: "Doe", Email: "john@example.com", Username: "johndoe", - Role: "student", + Role: testStudent, }, CreatedAt: time.Now(), }, @@ -518,8 +523,8 @@ func TestApproveRegistrationRequest(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -669,8 +674,8 @@ func TestRemoveTaskFromContest(t *testing.T) { // Mock user and add to context mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := r.Context() ctx = context.WithValue(ctx, httputils.UserKey, mockUser) diff --git a/internal/api/http/routes/contests_test.go b/internal/api/http/routes/contests_test.go index 2eb5c0d8..06191916 100644 --- a/internal/api/http/routes/contests_test.go +++ b/internal/api/http/routes/contests_test.go @@ -55,8 +55,8 @@ func TestGetContest(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -145,8 +145,8 @@ func TestRegisterForContest(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "student", - Email: "test@example.com", + Role: testStudent, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -397,8 +397,8 @@ func TestGetMyContestResults(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "student", - Email: "test@example.com", + Role: testStudent, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -522,8 +522,8 @@ func TestGetContestTasks(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "student", - Email: "test@example.com", + Role: testStudent, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) diff --git a/internal/api/http/routes/groups_test.go b/internal/api/http/routes/groups_test.go index e1bccd6f..36f55e03 100644 --- a/internal/api/http/routes/groups_test.go +++ b/internal/api/http/routes/groups_test.go @@ -57,8 +57,8 @@ func TestCreateGroup(t *testing.T) { // Mock user and add to context mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := r.Context() ctx = context.WithValue(ctx, httputils.UserKey, mockUser) @@ -114,7 +114,7 @@ func TestCreateGroup(t *testing.T) { t.Run("Not authorized", func(t *testing.T) { body := schemas.CreateGroup{ - Name: "Test Group", + Name: testGroupName, } jsonBody, err := json.Marshal(body) if err != nil { @@ -122,7 +122,7 @@ func TestCreateGroup(t *testing.T) { } expectedGroup := &schemas.Group{ - Name: "Test Group", + Name: testGroupName, CreatedBy: 1, // Match the mock user ID } @@ -152,7 +152,7 @@ func TestCreateGroup(t *testing.T) { t.Run("Internal server error", func(t *testing.T) { body := schemas.CreateGroup{ - Name: "Test Group", + Name: testGroupName, } jsonBody, err := json.Marshal(body) if err != nil { @@ -180,7 +180,7 @@ func TestCreateGroup(t *testing.T) { t.Run("Success", func(t *testing.T) { body := schemas.CreateGroup{ - Name: "Test Group", + Name: testGroupName, } jsonBody, err := json.Marshal(body) if err != nil { @@ -189,7 +189,7 @@ func TestCreateGroup(t *testing.T) { gs.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( func(db database.Database, user schemas.User, group *schemas.Group) (int64, error) { - assert.Equal(t, "Test Group", group.Name) + assert.Equal(t, testGroupName, group.Name) assert.Equal(t, int64(1), group.CreatedBy) return 1, nil }).Times(1) @@ -230,8 +230,8 @@ func TestGetGroup(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -301,7 +301,7 @@ func TestGetGroup(t *testing.T) { t.Run("Success", func(t *testing.T) { group := schemas.GroupDetailed{ ID: 1, - Name: "Test Group", + Name: testGroupName, CreatedBy: 1, } gs.EXPECT().Get(gomock.Any(), gomock.Any(), int64(1)).Return(&group, nil).Times(1) @@ -338,8 +338,8 @@ func TestGetAllGroup(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } // Simulate query params middleware - convert strings to ints @@ -461,8 +461,8 @@ func TestEditGroup(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -526,7 +526,7 @@ func TestEditGroup(t *testing.T) { }) t.Run("Internal server error", func(t *testing.T) { - name := "Test Group" + name := testGroupName body := schemas.EditGroup{Name: &name} jsonBody, _ := json.Marshal(body) @@ -591,8 +591,8 @@ func TestAddUsersToGroup(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -765,8 +765,8 @@ func TestDeleteUsersFromGroup(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -940,8 +940,8 @@ func TestGetGroupUsers(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -1025,8 +1025,8 @@ func TestGetGroupUsers(t *testing.T) { t.Run("Success", func(t *testing.T) { users := []schemas.User{ - {ID: 1, Email: "test@example.com"}, - {ID: 2, Email: "test@example.com"}, + {ID: 1, Email: testExampleMail}, + {ID: 2, Email: testExampleMail}, } gs.EXPECT().GetUsers(gomock.Any(), gomock.Any(), int64(1)).Return(users, nil).Times(1) resp, err := http.Get(server.URL + "/1/users") diff --git a/internal/api/http/routes/submissions_test.go b/internal/api/http/routes/submissions_test.go index 0a39a46c..3ee27181 100644 --- a/internal/api/http/routes/submissions_test.go +++ b/internal/api/http/routes/submissions_test.go @@ -40,8 +40,8 @@ func TestGetAll(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := r.Context() ctx = context.WithValue(ctx, httputils.UserKey, mockUser) @@ -136,8 +136,8 @@ func TestGetByID(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -217,8 +217,8 @@ func TestGetAllForTask(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "admin", - Email: "test@example.com", + Role: testAdmin, + Email: testExampleMail, } ctx := r.Context() ctx = context.WithValue(ctx, httputils.UserKey, mockUser) @@ -343,8 +343,8 @@ func TestSubmitSolution(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "student", - Email: "test@example.com", + Role: testStudent, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) diff --git a/internal/api/http/routes/tasks_management_test.go b/internal/api/http/routes/tasks_management_test.go index 7ee71d09..bc6a0590 100644 --- a/internal/api/http/routes/tasks_management_test.go +++ b/internal/api/http/routes/tasks_management_test.go @@ -41,8 +41,8 @@ func TestDeleteTask(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "teacher", - Email: "test@example.com", + Role: testTeacher, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -173,8 +173,8 @@ func TestEditTask(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "teacher", - Email: "test@example.com", + Role: testTeacher, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -315,8 +315,8 @@ func TestGetAllCreatedTasks(t *testing.T) { }() mockUser := schemas.User{ ID: 1, - Role: "teacher", - Email: "test@example.com", + Role: testTeacher, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) ctx = context.WithValue(ctx, httputils.QueryParamsKey, map[string]any{ @@ -460,8 +460,8 @@ func TestGetLimits(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "teacher", - Email: "test@example.com", + Role: testTeacher, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) @@ -596,8 +596,8 @@ func TestPutLimits(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mockUser := schemas.User{ ID: 1, - Role: "teacher", - Email: "test@example.com", + Role: testTeacher, + Email: testExampleMail, } ctx := context.WithValue(r.Context(), httputils.UserKey, mockUser) handler.ServeHTTP(w, r.WithContext(ctx)) diff --git a/internal/api/http/routes/users_test.go b/internal/api/http/routes/users_test.go index aa087d67..3ee097de 100644 --- a/internal/api/http/routes/users_test.go +++ b/internal/api/http/routes/users_test.go @@ -23,6 +23,11 @@ import ( "gorm.io/gorm" ) +const ( + oldPass = "OldPass123!" + newPass = "NewPass123!" +) + func TestGetAllUsers(t *testing.T) { // Setup ctrl := gomock.NewController(t) @@ -92,7 +97,7 @@ func TestGetAllUsers(t *testing.T) { defer server.Close() expectedUsers := []schemas.User{ - {ID: 1, Name: "User1", Email: "user1@email.com", Role: types.UserRoleStudent}, + {ID: 1, Name: testUserName, Email: testUser1Email, Role: types.UserRoleStudent}, {ID: 2, Name: "User2", Email: "user2@email.com", Role: types.UserRoleAdmin}, } @@ -239,8 +244,8 @@ func TestGetUserByID(t *testing.T) { expectedUser := &schemas.User{ ID: 1, Name: "Test", - Surname: "User", - Email: "test@email.com", + Surname: testSurname, + Email: testUserEmail, Role: types.UserRoleStudent, } @@ -269,7 +274,7 @@ func TestEditUser(t *testing.T) { currentUser := schemas.User{ ID: 1, Name: "Current", - Surname: "User", + Surname: testSurname, Email: "current@email.com", Role: types.UserRoleAdmin, } @@ -451,7 +456,7 @@ func TestChangePassword(t *testing.T) { currentUser := schemas.User{ ID: 1, Name: "Current", - Surname: "User", + Surname: testSurname, Email: "current@email.com", Role: types.UserRoleAdmin, } @@ -489,9 +494,9 @@ func TestChangePassword(t *testing.T) { route.ChangePassword(w, r.WithContext(ctx)) }) reqBody := schemas.UserChangePassword{ - OldPassword: "OldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + OldPassword: oldPass, + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/password", bytes.NewBuffer(jsonBody)) @@ -510,9 +515,9 @@ func TestChangePassword(t *testing.T) { route.ChangePassword(w, r.WithContext(ctx)) }) reqBody := schemas.UserChangePassword{ - OldPassword: "OldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + OldPassword: oldPass, + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/abc/password", bytes.NewBuffer(jsonBody)) @@ -548,9 +553,9 @@ func TestChangePassword(t *testing.T) { route.ChangePassword(w, r.WithContext(ctx)) }) reqBody := schemas.UserChangePassword{ - OldPassword: "OldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + OldPassword: oldPass, + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/999/password", bytes.NewBuffer(jsonBody)) @@ -572,9 +577,9 @@ func TestChangePassword(t *testing.T) { route.ChangePassword(w, r.WithContext(ctx)) }) reqBody := schemas.UserChangePassword{ - OldPassword: "OldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + OldPassword: oldPass, + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/2/password", bytes.NewBuffer(jsonBody)) @@ -596,9 +601,9 @@ func TestChangePassword(t *testing.T) { route.ChangePassword(w, r.WithContext(ctx)) }) reqBody := schemas.UserChangePassword{ - OldPassword: "OldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + OldPassword: oldPass, + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/2/password", bytes.NewBuffer(jsonBody)) @@ -621,8 +626,8 @@ func TestChangePassword(t *testing.T) { }) reqBody := schemas.UserChangePassword{ OldPassword: "WrongOldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/1/password", bytes.NewBuffer(jsonBody)) @@ -644,9 +649,9 @@ func TestChangePassword(t *testing.T) { route.ChangePassword(w, r.WithContext(ctx)) }) reqBody := schemas.UserChangePassword{ - OldPassword: "OldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + OldPassword: oldPass, + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/1/password", bytes.NewBuffer(jsonBody)) @@ -668,9 +673,9 @@ func TestChangePassword(t *testing.T) { route.ChangePassword(w, r.WithContext(ctx)) }) reqBody := schemas.UserChangePassword{ - OldPassword: "OldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + OldPassword: oldPass, + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/1/password", bytes.NewBuffer(jsonBody)) @@ -692,9 +697,9 @@ func TestChangePassword(t *testing.T) { route.ChangePassword(w, r.WithContext(ctx)) }) reqBody := schemas.UserChangePassword{ - OldPassword: "OldPass123!", - NewPassword: "NewPass123!", - NewPasswordConfirm: "NewPass123!", + OldPassword: oldPass, + NewPassword: newPass, + NewPasswordConfirm: newPass, } jsonBody, _ := json.Marshal(reqBody) req := httptest.NewRequest(http.MethodPatch, "/1/password", bytes.NewBuffer(jsonBody)) @@ -775,9 +780,9 @@ func TestGetMe(t *testing.T) { adminUser := schemas.User{ ID: 2, Name: "Admin", - Surname: "User", + Surname: testSurname, Email: "admin@example.com", - Username: "admin", + Username: testAdmin, Role: types.UserRoleAdmin, } diff --git a/internal/config/config.go b/internal/config/config.go index 23c95062..2b3b6169 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -70,6 +70,33 @@ const ( trueValue = "true" ) +// Environment variable names read by NewConfig. +const ( + envDBHost = "DB_HOST" + envDBPort = "DB_PORT" + envDBUser = "DB_USER" + envDBPassword = "DB_PASSWORD" + envDBName = "DB_NAME" + envAPPPort = "APP_PORT" + envRefreshToken = "API_REFRESH_TOKEN_PATH" + envAccessTokenMin = "JWT_ACCESS_TOKEN_MINUTES" + envCookieSecure = "COOKIE_SECURE" + envFileStorageHost = "FILE_STORAGE_HOST" + envFileStoragePort = "FILE_STORAGE_PORT" + envFileStoragePub = "FILE_STORAGE_PUBLIC_URL" + envQueueName = "QUEUE_NAME" + envResponseQueue = "RESPONSE_QUEUE_NAME" + envQueueHost = "QUEUE_HOST" + envQueuePort = "QUEUE_PORT" + envQueueUser = "QUEUE_USER" + envQueuePassword = "QUEUE_PASSWORD" + envJWTSecretKey = "JWT_SECRET_KEY" + envDump = "DUMP" + envCORSOrigins = "CORS_ALLOWED_ORIGINS" + envCORSCredentials = "CORS_ALLOW_CREDENTIALS" + envSignedURLTTL = "SIGNED_URL_TTL_SECONDS" +) + // NewConfig creates new Config instance // // It reads environment variables and returns Config instance. Available environment variables: @@ -117,44 +144,44 @@ const ( func NewConfig() *Config { log := utils.NewNamedLogger("config") - dbHost := os.Getenv("DB_HOST") + dbHost := os.Getenv(envDBHost) if dbHost == "" { - log.Warnf("DB_HOST is not set. Using default value %s", "localhost") + log.Warnf(envDBHost+" is not set. Using default value %s", "localhost") } - dbPortStr := os.Getenv("DB_PORT") + dbPortStr := os.Getenv(envDBPort) if dbPortStr == "" { - log.Panic("DB_PORT is not set") + log.Panic(envDBPort + " is not set") } dbPort := validatePort(dbPortStr, "database", log) - dbUser := os.Getenv("DB_USER") + dbUser := os.Getenv(envDBUser) if dbUser == "" { - log.Panic("DB_USER is not set") + log.Panic(envDBUser + " is not set") } - dbPassword := os.Getenv("DB_PASSWORD") + dbPassword := os.Getenv(envDBPassword) if dbPassword == "" { - log.Warnf("DB_PASSWORD is not set. Using empty password") + log.Warnf(envDBPassword + " is not set. Using empty password") } - dbName := os.Getenv("DB_NAME") + dbName := os.Getenv(envDBName) if dbName == "" { - log.Panic("DB_NAME is not set") + log.Panic(envDBName + " is not set") } - appPortStr := os.Getenv("APP_PORT") + appPortStr := os.Getenv(envAPPPort) if appPortStr == "" { - log.Warnf("API_PORT is not set. Using default port %s", defaultAPIPort) + log.Warnf(envAPPPort+" is not set. Using default port %s", defaultAPIPort) appPortStr = defaultAPIPort } appPort := validatePort(appPortStr, "application", log) - refreshTokenPath := os.Getenv("API_REFRESH_TOKEN_PATH") + refreshTokenPath := os.Getenv(envRefreshToken) if refreshTokenPath == "" { - log.Warnf("API_REFRESH_TOKEN_PATH is not set. Using default path %s", defaultAPIRefreshTokenPath) + log.Warnf(envRefreshToken+" is not set. Using default path %s", defaultAPIRefreshTokenPath) refreshTokenPath = defaultAPIRefreshTokenPath } - accessTokenMinutesStr := os.Getenv("JWT_ACCESS_TOKEN_MINUTES") + accessTokenMinutesStr := os.Getenv(envAccessTokenMin) if accessTokenMinutesStr == "" { - log.Warnf("JWT_ACCESS_TOKEN_MINUTES is not set. Using default value %s", defaultAccessTokenMinutesStr) + log.Warnf(envAccessTokenMin+" is not set. Using default value %s", defaultAccessTokenMinutesStr) accessTokenMinutesStr = defaultAccessTokenMinutesStr } accessTokenMinutesParsed, err := strconv.ParseUint(accessTokenMinutesStr, 10, 16) @@ -163,78 +190,78 @@ func NewConfig() *Config { } accessTokenMinutes := uint16(accessTokenMinutesParsed) - cookieSecure := os.Getenv("COOKIE_SECURE") == trueValue + cookieSecure := os.Getenv(envCookieSecure) == trueValue - fileStorageHost := os.Getenv("FILE_STORAGE_HOST") + fileStorageHost := os.Getenv(envFileStorageHost) if fileStorageHost == "" { - log.Panic("FILE_STORAGE_HOST is not set") + log.Panic(envFileStorageHost + " is not set") } - fileStoragePortStr := os.Getenv("FILE_STORAGE_PORT") + fileStoragePortStr := os.Getenv(envFileStoragePort) if fileStoragePortStr == "" { - log.Panic("FILE_STORAGE_PORT is not set") + log.Panic(envFileStoragePort + " is not set") } _ = validatePort(fileStoragePortStr, "file storage", log) fileStorageURL := "http://" + fileStorageHost + ":" + fileStoragePortStr - fileStoragePublicURL := strings.TrimSuffix(os.Getenv("FILE_STORAGE_PUBLIC_URL"), "/") + fileStoragePublicURL := strings.TrimSuffix(os.Getenv(envFileStoragePub), "/") if fileStoragePublicURL == "" { - log.Warnf("FILE_STORAGE_PUBLIC_URL is not set. Signed URLs will use internal address %s and will not be reachable by browsers", fileStorageURL) + log.Warnf(envFileStoragePub+" is not set. Signed URLs will use internal address %s and will not be reachable by browsers", fileStorageURL) fileStoragePublicURL = fileStorageURL } - queueName := os.Getenv("QUEUE_NAME") + queueName := os.Getenv(envQueueName) if queueName == "" { - log.Warnf("QUEUE_NAME is not set. Using default queue name %s", defaultQueueName) + log.Warnf(envQueueName+" is not set. Using default queue name %s", defaultQueueName) queueName = defaultQueueName } - responseQueueName := os.Getenv("RESPONSE_QUEUE_NAME") + responseQueueName := os.Getenv(envResponseQueue) if responseQueueName == "" { - log.Warnf("RESPONSE_QUEUE_NAME is not set. Using default response queue name %s", defaultResponseQueueName) + log.Warnf(envResponseQueue+" is not set. Using default response queue name %s", defaultResponseQueueName) responseQueueName = defaultResponseQueueName } - queueHost := os.Getenv("QUEUE_HOST") + queueHost := os.Getenv(envQueueHost) if queueHost == "" { - log.Panic("QUEUE_HOST is not set") + log.Panic(envQueueHost + " is not set") } - queuePortStr := os.Getenv("QUEUE_PORT") + queuePortStr := os.Getenv(envQueuePort) if queuePortStr == "" { - log.Panic("QUEUE_PORT is not set") + log.Panic(envQueuePort + " is not set") } queuePort := validatePort(queuePortStr, "broker", log) - queueUser := os.Getenv("QUEUE_USER") + queueUser := os.Getenv(envQueueUser) if queueUser == "" { - log.Panic("QUEUE_USER is not set") + log.Panic(envQueueUser + " is not set") } - queuePassword := os.Getenv("QUEUE_PASSWORD") + queuePassword := os.Getenv(envQueuePassword) if queuePassword == "" { - log.Panic("QUEUE_PASSWORD is not set") + log.Panic(envQueuePassword + " is not set") } - jwtSecretKey := os.Getenv("JWT_SECRET_KEY") + jwtSecretKey := os.Getenv(envJWTSecretKey) if jwtSecretKey == "" { - log.Panic("JWT_SECRET_KEY is not set") + log.Panic(envJWTSecretKey + " is not set") } - dumpStr := os.Getenv("DUMP") + dumpStr := os.Getenv(envDump) dump := dumpStr == trueValue - corsAllowedOrigins := os.Getenv("CORS_ALLOWED_ORIGINS") + corsAllowedOrigins := os.Getenv(envCORSOrigins) if corsAllowedOrigins == "" { - log.Warnf("CORS_ALLOWED_ORIGINS is not set. Using default value %s", defaultCORSAllowedOrigins) + log.Warnf(envCORSOrigins+" is not set. Using default value %s", defaultCORSAllowedOrigins) corsAllowedOrigins = defaultCORSAllowedOrigins } - corsAllowCredentials := os.Getenv("CORS_ALLOW_CREDENTIALS") == trueValue + corsAllowCredentials := os.Getenv(envCORSCredentials) == trueValue if corsAllowCredentials && corsAllowedOrigins == "*" { log.Panicf(`CORS_ALLOWED_ORIGINS=* and CORS_ALLOW_CREDENTIALS=true cannot be set at the same time. More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials`) } - signedURLTTLSecondsStr := os.Getenv("SIGNED_URL_TTL_SECONDS") + signedURLTTLSecondsStr := os.Getenv(envSignedURLTTL) if signedURLTTLSecondsStr == "" { - log.Warnf("SIGNED_URL_TTL_SECONDS is not set. Using default value %s", defaultSignedURLTTLSecondsStr) + log.Warnf(envSignedURLTTL+" is not set. Using default value %s", defaultSignedURLTTLSecondsStr) signedURLTTLSecondsStr = defaultSignedURLTTLSecondsStr } signedURLTTLSecondsParsed, err := strconv.ParseUint(signedURLTTLSecondsStr, 10, 16) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 7f0d584b..821df931 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -9,46 +9,48 @@ import ( "github.com/stretchr/testify/require" ) +const baseDBUser = "user" + // All environment variables used by NewConfig. var allEnvVars = []string{ - "DB_HOST", - "DB_PORT", - "DB_USER", - "DB_PASSWORD", - "DB_NAME", - "APP_PORT", - "API_REFRESH_TOKEN_PATH", - "FILE_STORAGE_HOST", - "FILE_STORAGE_PORT", - "QUEUE_NAME", - "RESPONSE_QUEUE_NAME", - "QUEUE_HOST", - "QUEUE_PORT", - "QUEUE_USER", - "QUEUE_PASSWORD", - "JWT_SECRET_KEY", - "DUMP", + envDBHost, + envDBPort, + envDBUser, + envDBPassword, + envDBName, + envAPPPort, + envRefreshToken, + envFileStorageHost, + envFileStoragePort, + envQueueName, + envResponseQueue, + envQueueHost, + envQueuePort, + envQueueUser, + envQueuePassword, + envJWTSecretKey, + envDump, } // Base valid environment values. var baseEnv = map[string]string{ - "DB_HOST": "localhost", - "DB_PORT": "5432", - "DB_USER": "user", - "DB_PASSWORD": "pass", - "DB_NAME": "appdb", - "APP_PORT": "9090", - "API_REFRESH_TOKEN_PATH": "/api/v1/auth/refresh-custom", - "FILE_STORAGE_HOST": "filesvc", - "FILE_STORAGE_PORT": "9100", - "QUEUE_NAME": "custom_worker_queue", - "RESPONSE_QUEUE_NAME": "custom_worker_response_queue", - "QUEUE_HOST": "queuehost", - "QUEUE_PORT": "5673", - "QUEUE_USER": "queueuser", - "QUEUE_PASSWORD": "queuepass", - "JWT_SECRET_KEY": "supersecret", - "DUMP": "true", + envDBHost: "localhost", + envDBPort: "5432", + envDBUser: baseDBUser, + envDBPassword: "pass", + envDBName: "appdb", + envAPPPort: "9090", + envRefreshToken: "/api/v1/auth/refresh-custom", + envFileStorageHost: "filesvc", + envFileStoragePort: "9100", + envQueueName: "custom_worker_queue", + envResponseQueue: "custom_worker_response_queue", + envQueueHost: "queuehost", + envQueuePort: "5673", + envQueueUser: "queueuser", + envQueuePassword: "queuepass", + envJWTSecretKey: "supersecret", + envDump: "true", } func unsetAll() { @@ -71,29 +73,29 @@ func TestNewConfig_SuccessFullEnv(t *testing.T) { require.NotNil(t, cfg) // DB - require.Equal(t, baseEnv["DB_HOST"], cfg.DB.Host) + require.Equal(t, baseEnv[envDBHost], cfg.DB.Host) require.Equal(t, uint16(5432), cfg.DB.Port) - require.Equal(t, baseEnv["DB_USER"], cfg.DB.User) - require.Equal(t, baseEnv["DB_PASSWORD"], cfg.DB.Password) - require.Equal(t, baseEnv["DB_NAME"], cfg.DB.Name) + require.Equal(t, baseEnv[envDBUser], cfg.DB.User) + require.Equal(t, baseEnv[envDBPassword], cfg.DB.Password) + require.Equal(t, baseEnv[envDBName], cfg.DB.Name) // API require.Equal(t, uint16(9090), cfg.API.Port) - require.Equal(t, baseEnv["API_REFRESH_TOKEN_PATH"], cfg.API.RefreshTokenPath) + require.Equal(t, baseEnv[envRefreshToken], cfg.API.RefreshTokenPath) // Broker - require.Equal(t, baseEnv["QUEUE_NAME"], cfg.Broker.QueueName) - require.Equal(t, baseEnv["RESPONSE_QUEUE_NAME"], cfg.Broker.ResponseQueueName) - require.Equal(t, baseEnv["QUEUE_HOST"], cfg.Broker.Host) + require.Equal(t, baseEnv[envQueueName], cfg.Broker.QueueName) + require.Equal(t, baseEnv[envResponseQueue], cfg.Broker.ResponseQueueName) + require.Equal(t, baseEnv[envQueueHost], cfg.Broker.Host) require.Equal(t, uint16(5673), cfg.Broker.Port) - require.Equal(t, baseEnv["QUEUE_USER"], cfg.Broker.User) - require.Equal(t, baseEnv["QUEUE_PASSWORD"], cfg.Broker.Password) + require.Equal(t, baseEnv[envQueueUser], cfg.Broker.User) + require.Equal(t, baseEnv[envQueuePassword], cfg.Broker.Password) // File storage URL composition - require.Equal(t, "http://"+baseEnv["FILE_STORAGE_HOST"]+":"+baseEnv["FILE_STORAGE_PORT"], cfg.FileStorageURL) + require.Equal(t, "http://"+baseEnv[envFileStorageHost]+":"+baseEnv[envFileStoragePort], cfg.FileStorageURL) // JWT - require.Equal(t, baseEnv["JWT_SECRET_KEY"], cfg.JWTSecretKey) + require.Equal(t, baseEnv[envJWTSecretKey], cfg.JWTSecretKey) // Dump flag require.True(t, cfg.Dump) @@ -104,16 +106,16 @@ func TestNewConfig_DefaultsAndOptionalMissing(t *testing.T) { // Required variables only (omit optional ones) minimal := map[string]string{ - "DB_PORT": "5432", - "DB_USER": "user", - "DB_NAME": "db", - "FILE_STORAGE_HOST": "fs", - "FILE_STORAGE_PORT": "9000", - "QUEUE_HOST": "qhost", - "QUEUE_PORT": "5672", - "QUEUE_USER": "quser", - "QUEUE_PASSWORD": "qpass", - "JWT_SECRET_KEY": "secret", + envDBPort: "5432", + envDBUser: baseDBUser, + envDBName: "db", + envFileStorageHost: "fs", + envFileStoragePort: "9000", + envQueueHost: "qhost", + envQueuePort: "5672", + envQueueUser: "quser", + envQueuePassword: "qpass", + envJWTSecretKey: "secret", // Omit APP_PORT, API_REFRESH_TOKEN_PATH, QUEUE_NAME, RESPONSE_QUEUE_NAME, DB_HOST, DB_PASSWORD, DUMP } setEnv(minimal) @@ -139,16 +141,16 @@ func TestNewConfig_DefaultsAndOptionalMissing(t *testing.T) { func TestNewConfig_DumpFlagFalseWhenMissing(t *testing.T) { unsetAll() env := map[string]string{ - "DB_PORT": "5432", - "DB_USER": "user", - "DB_NAME": "db", - "FILE_STORAGE_HOST": "fs", - "FILE_STORAGE_PORT": "9000", - "QUEUE_HOST": "qhost", - "QUEUE_PORT": "5672", - "QUEUE_USER": "quser", - "QUEUE_PASSWORD": "qpass", - "JWT_SECRET_KEY": "secret", + envDBPort: "5432", + envDBUser: baseDBUser, + envDBName: "db", + envFileStorageHost: "fs", + envFileStoragePort: "9000", + envQueueHost: "qhost", + envQueuePort: "5672", + envQueueUser: "quser", + envQueuePassword: "qpass", + envJWTSecretKey: "secret", } setEnv(env) @@ -159,16 +161,16 @@ func TestNewConfig_DumpFlagFalseWhenMissing(t *testing.T) { func TestNewConfig_PanicsWhenRequiredMissing(t *testing.T) { requiredMissing := []string{ - "DB_PORT", - "DB_USER", - "DB_NAME", - "FILE_STORAGE_HOST", - "FILE_STORAGE_PORT", - "QUEUE_HOST", - "QUEUE_PORT", - "QUEUE_USER", - "QUEUE_PASSWORD", - "JWT_SECRET_KEY", + envDBPort, + envDBUser, + envDBName, + envFileStorageHost, + envFileStoragePort, + envQueueHost, + envQueuePort, + envQueueUser, + envQueuePassword, + envJWTSecretKey, } for _, missing := range requiredMissing { @@ -198,23 +200,23 @@ func TestNewConfig_PanicsOnInvalidPortValues(t *testing.T) { }{ { name: "invalid DB_PORT", - vars: map[string]string{"DB_PORT": "notint"}, + vars: map[string]string{envDBPort: "notint"}, }, { name: "invalid APP_PORT", - vars: map[string]string{"APP_PORT": "invalid"}, + vars: map[string]string{envAPPPort: "invalid"}, }, { name: "invalid FILE_STORAGE_PORT", - vars: map[string]string{"FILE_STORAGE_PORT": "bad"}, + vars: map[string]string{envFileStoragePort: "bad"}, }, { name: "invalid QUEUE_PORT", - vars: map[string]string{"QUEUE_PORT": "oops"}, + vars: map[string]string{envQueuePort: "oops"}, }, { name: "out_of_range DB_PORT", - vars: map[string]string{"DB_PORT": "70000"}, + vars: map[string]string{envDBPort: "70000"}, }, } diff --git a/package/filestorage/errors_test.go b/package/filestorage/errors_test.go index 69b7efc7..cc678b7c 100644 --- a/package/filestorage/errors_test.go +++ b/package/filestorage/errors_test.go @@ -9,6 +9,15 @@ import ( "github.com/stretchr/testify/require" ) +const ( + testRuleName = "test-rule" + testRuleMessage = "test message" + contextKey = "key" + testArchivePath = "/path/to/archive.zip" + testDecompressMessage = "failed to decompress" + contextValue = "value" +) + // Export internal types for testing // This file is only included in test builds @@ -61,10 +70,10 @@ func (f *TestableFileStorageService) SetBucketName(name string) { func TestValidationError(t *testing.T) { t.Run("Error returns formatted string without cause", func(t *testing.T) { err := &ValidationError{ - RuleName: "test-rule", - Message: "test message", + RuleName: testRuleName, + Message: testRuleMessage, Cause: nil, - Context: map[string]any{"key": "value"}, + Context: map[string]any{contextKey: contextValue}, } expected := "validation error [test-rule]: test message" @@ -74,10 +83,10 @@ func TestValidationError(t *testing.T) { t.Run("Error returns formatted string with cause", func(t *testing.T) { cause := errors.New("underlying error") err := &ValidationError{ - RuleName: "test-rule", - Message: "test message", + RuleName: testRuleName, + Message: testRuleMessage, Cause: cause, - Context: map[string]any{"key": "value"}, + Context: map[string]any{contextKey: contextValue}, } expected := "validation error [test-rule]: test message: underlying error" @@ -87,8 +96,8 @@ func TestValidationError(t *testing.T) { t.Run("Unwrap returns cause", func(t *testing.T) { cause := errors.New("underlying error") err := &ValidationError{ - RuleName: "test-rule", - Message: "test message", + RuleName: testRuleName, + Message: testRuleMessage, Cause: cause, } @@ -98,8 +107,8 @@ func TestValidationError(t *testing.T) { t.Run("Unwrap returns nil when no cause", func(t *testing.T) { err := &ValidationError{ - RuleName: "test-rule", - Message: "test message", + RuleName: testRuleName, + Message: testRuleMessage, Cause: nil, } @@ -110,8 +119,8 @@ func TestValidationError(t *testing.T) { t.Run("Error is compatible with errors.Is", func(t *testing.T) { cause := errors.New("specific error") err := &ValidationError{ - RuleName: "test-rule", - Message: "test message", + RuleName: testRuleName, + Message: testRuleMessage, Cause: cause, } @@ -120,24 +129,24 @@ func TestValidationError(t *testing.T) { t.Run("Error is compatible with errors.As", func(t *testing.T) { err := &ValidationError{ - RuleName: "test-rule", - Message: "test message", + RuleName: testRuleName, + Message: testRuleMessage, Cause: nil, } var validationErr *ValidationError require.ErrorAs(t, err, &validationErr) - assert.Equal(t, "test-rule", validationErr.RuleName) + assert.Equal(t, testRuleName, validationErr.RuleName) }) } func TestDecompressionError(t *testing.T) { t.Run("Error returns formatted string without cause", func(t *testing.T) { err := &DecompressionError{ - ArchivePath: "/path/to/archive.zip", - Message: "failed to decompress", + ArchivePath: testArchivePath, + Message: testDecompressMessage, Cause: nil, - Context: map[string]any{"key": "value"}, + Context: map[string]any{contextKey: contextValue}, } expected := "decompression error: failed to decompress" @@ -147,10 +156,10 @@ func TestDecompressionError(t *testing.T) { t.Run("Error returns formatted string with cause", func(t *testing.T) { cause := errors.New("underlying error") err := &DecompressionError{ - ArchivePath: "/path/to/archive.zip", - Message: "failed to decompress", + ArchivePath: testArchivePath, + Message: testDecompressMessage, Cause: cause, - Context: map[string]any{"key": "value"}, + Context: map[string]any{contextKey: contextValue}, } expected := "decompression error: failed to decompress: underlying error" @@ -160,8 +169,8 @@ func TestDecompressionError(t *testing.T) { t.Run("Unwrap returns cause", func(t *testing.T) { cause := errors.New("underlying error") err := &DecompressionError{ - ArchivePath: "/path/to/archive.zip", - Message: "failed to decompress", + ArchivePath: testArchivePath, + Message: testDecompressMessage, Cause: cause, } @@ -171,8 +180,8 @@ func TestDecompressionError(t *testing.T) { t.Run("Unwrap returns nil when no cause", func(t *testing.T) { err := &DecompressionError{ - ArchivePath: "/path/to/archive.zip", - Message: "failed to decompress", + ArchivePath: testArchivePath, + Message: testDecompressMessage, Cause: nil, } @@ -183,8 +192,8 @@ func TestDecompressionError(t *testing.T) { t.Run("Error is compatible with errors.Is", func(t *testing.T) { cause := errors.New("specific error") err := &DecompressionError{ - ArchivePath: "/path/to/archive.zip", - Message: "failed to decompress", + ArchivePath: testArchivePath, + Message: testDecompressMessage, Cause: cause, } @@ -193,13 +202,13 @@ func TestDecompressionError(t *testing.T) { t.Run("Error is compatible with errors.As", func(t *testing.T) { err := &DecompressionError{ - ArchivePath: "/path/to/archive.zip", - Message: "failed to decompress", + ArchivePath: testArchivePath, + Message: testDecompressMessage, Cause: nil, } var decompressionErr *DecompressionError require.ErrorAs(t, err, &decompressionErr) - assert.Equal(t, "/path/to/archive.zip", decompressionErr.ArchivePath) + assert.Equal(t, testArchivePath, decompressionErr.ArchivePath) }) } diff --git a/package/filestorage/service.go b/package/filestorage/service.go index 2eb6160d..b0bb5f58 100644 --- a/package/filestorage/service.go +++ b/package/filestorage/service.go @@ -21,7 +21,23 @@ import ( "go.uber.org/zap" ) -const descriptionFilename = "description.pdf" +const ( + descriptionFilename = "description.pdf" + + contextKeyDestination = "destination" + contextKeyDirectoryPath = "directory_path" + contextKeyDirectoryName = "directory_name" + contextKeyFilePath = "file_path" + contextKeyFileName = "file_name" + contextKeyFolderPath = "folder_path" + contextKeyZipEntry = "zip_entry" + + inputDirectoryName = "input" + outputDirectoryName = "output" + acceptedTextExt = ".txt" + acceptedInputExt = ".in" + acceptedOutputExt = ".out" +) type UploadedFile struct { Path string `json:"path"` @@ -88,7 +104,7 @@ func (d *decompressor) DecompressArchive(archivePath string, pattern string) (st Message: "failed to decompress gzip archive", Cause: err, Context: map[string]any{ - "destination": folderPath, + contextKeyDestination: folderPath, }, } } @@ -100,7 +116,7 @@ func (d *decompressor) DecompressArchive(archivePath string, pattern string) (st Message: "failed to decompress zip archive", Cause: err, Context: map[string]any{ - "destination": folderPath, + contextKeyDestination: folderPath, }, } } @@ -127,7 +143,7 @@ func (d *decompressor) decompressGzip(archivePath string, newPath string) error Message: "failed to open archive file", Cause: err, Context: map[string]any{ - "destination": newPath, + contextKeyDestination: newPath, }, } } @@ -140,7 +156,7 @@ func (d *decompressor) decompressGzip(archivePath string, newPath string) error Message: "failed to create gzip reader", Cause: err, Context: map[string]any{ - "destination": newPath, + contextKeyDestination: newPath, }, } } @@ -159,7 +175,7 @@ func (d *decompressor) decompressGzip(archivePath string, newPath string) error Message: "failed to read tar entry", Cause: err, Context: map[string]any{ - "destination": newPath, + contextKeyDestination: newPath, }, } } @@ -173,8 +189,8 @@ func (d *decompressor) decompressGzip(archivePath string, newPath string) error Message: "failed to create directory", Cause: err, Context: map[string]any{ - "directory_path": dirPath, - "header_name": header.Name, + contextKeyDirectoryPath: dirPath, + "header_name": header.Name, }, } } @@ -188,7 +204,7 @@ func (d *decompressor) decompressGzip(archivePath string, newPath string) error Cause: err, Context: map[string]any{ "parent_directory": path.Dir(filePath), - "file_path": filePath, + contextKeyFilePath: filePath, }, } } @@ -200,8 +216,8 @@ func (d *decompressor) decompressGzip(archivePath string, newPath string) error Message: "failed to create file", Cause: err, Context: map[string]any{ - "file_path": filePath, - "header_name": header.Name, + contextKeyFilePath: filePath, + "header_name": header.Name, }, } } @@ -213,7 +229,7 @@ func (d *decompressor) decompressGzip(archivePath string, newPath string) error Message: "failed to write file content", Cause: err, Context: map[string]any{ - "file_path": filePath, + contextKeyFilePath: filePath, }, } } @@ -224,8 +240,8 @@ func (d *decompressor) decompressGzip(archivePath string, newPath string) error Message: "unsupported file type in archive", Cause: nil, Context: map[string]any{ - "file_type": header.Typeflag, - "file_name": header.Name, + "file_type": header.Typeflag, + contextKeyFileName: header.Name, }, } } @@ -242,7 +258,7 @@ func (d *decompressor) decompressZip(archivePath string, newPath string) error { Message: "failed to open zip archive", Cause: err, Context: map[string]any{ - "destination": newPath, + contextKeyDestination: newPath, }, } } @@ -259,8 +275,8 @@ func (d *decompressor) decompressZip(archivePath string, newPath string) error { Message: "failed to create directory", Cause: err, Context: map[string]any{ - "directory_path": filePath, - "zip_entry": f.Name, + contextKeyDirectoryPath: filePath, + contextKeyZipEntry: f.Name, }, } } @@ -272,7 +288,7 @@ func (d *decompressor) decompressZip(archivePath string, newPath string) error { Cause: err, Context: map[string]any{ "parent_directory": filepath.Dir(filePath), - "file_path": filePath, + contextKeyFilePath: filePath, }, } } @@ -284,7 +300,7 @@ func (d *decompressor) decompressZip(archivePath string, newPath string) error { Message: fmt.Sprintf("failed to open file in zip: %s", f.Name), Cause: err, Context: map[string]any{ - "zip_entry": f.Name, + contextKeyZipEntry: f.Name, }, } } @@ -297,8 +313,8 @@ func (d *decompressor) decompressZip(archivePath string, newPath string) error { Message: "failed to create file", Cause: err, Context: map[string]any{ - "file_path": filePath, - "zip_entry": f.Name, + contextKeyFilePath: filePath, + contextKeyZipEntry: f.Name, }, } } @@ -310,8 +326,8 @@ func (d *decompressor) decompressZip(archivePath string, newPath string) error { Message: "failed to write file content", Cause: err, Context: map[string]any{ - "file_path": filePath, - "zip_entry": f.Name, + contextKeyFilePath: filePath, + contextKeyZipEntry: f.Name, }, } } @@ -336,20 +352,20 @@ func NewFileStorageService(fileStorageURL string, publicURL string, signedURLTTL // Configure validation rules validator.AddRule(&NonEmptyArchiveRule{}) validator.AddRule(&RequiredEntriesRule{ - RequiredEntries: []string{"input", "output", descriptionFilename}, + RequiredEntries: []string{inputDirectoryName, outputDirectoryName, descriptionFilename}, }) validator.AddRule(&InputOutputMatchRule{}) validator.AddRule(&DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt", ".in"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt, acceptedInputExt}, RequireSequential: true, }, }) validator.AddRule(&DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "output", - AcceptedExtensions: []string{".txt", ".out"}, + Name: outputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt, acceptedOutputExt}, RequireSequential: true, }, }) @@ -442,7 +458,7 @@ func (f *fileStorageService) UploadTask(taskID int64, archivePath string) (*Uplo } // Upload input files - inputFiles, err := f.uploadDirectoryFiles(folderPath, "input", taskBasePath) + inputFiles, err := f.uploadDirectoryFiles(folderPath, inputDirectoryName, taskBasePath) if err != nil { return nil, err } diff --git a/package/filestorage/service_test.go b/package/filestorage/service_test.go index 37d1965e..0bd09584 100644 --- a/package/filestorage/service_test.go +++ b/package/filestorage/service_test.go @@ -17,6 +17,8 @@ import ( "go.uber.org/mock/gomock" ) +const testZipFilename = "test.zip" + func TestDecompressor_DecompressArchive(t *testing.T) { d := NewDecompressor() @@ -328,7 +330,7 @@ func TestFileStorageServiceValidateArchiveStructure(t *testing.T) { service.SetValidator(mockValidator) - expectedArchivePath := "test.zip" + expectedArchivePath := testZipFilename err := service.ValidateArchiveStructure(expectedArchivePath) require.NoError(t, err) assert.Equal(t, expectedArchivePath, capturedCtx.ArchivePath) @@ -348,7 +350,7 @@ func TestFileStorageServiceValidateArchiveStructure(t *testing.T) { validator.AddRule(&NonEmptyArchiveRule{}) // Also add a rule that requires specific entries; with empty base, it'll fail validator.AddRule(&RequiredEntriesRule{ - RequiredEntries: []string{"description.pdf", "input/", "output/"}, + RequiredEntries: []string{descriptionFilename, "input/", "output/"}, }) service.SetValidator(validator) @@ -507,12 +509,12 @@ func TestUploadDescriptionFile_ErrorAndSuccess(t *testing.T) { t.Run("success -> uploads description.pdf", func(t *testing.T) { base := t.TempDir() - require.NoError(t, os.WriteFile(filepath.Join(base, "description.pdf"), []byte("pdf"), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(base, descriptionFilename), []byte("pdf"), 0644)) uploaded, err := svc.uploadDescriptionFile(base, "task/99") require.NoError(t, err) require.NotNil(t, uploaded) assert.Equal(t, "task/99/description.pdf", uploaded.Path) - assert.Equal(t, "description.pdf", uploaded.Filename) + assert.Equal(t, descriptionFilename, uploaded.Filename) assert.Equal(t, "maxit", uploaded.Bucket) }) } @@ -564,7 +566,7 @@ func TestUploadTask_EndToEnd_WithFakeDecompressor(t *testing.T) { // Build a decompressed folder structure base := t.TempDir() - require.NoError(t, os.WriteFile(filepath.Join(base, "description.pdf"), []byte("pdf"), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(base, descriptionFilename), []byte("pdf"), 0644)) require.NoError(t, os.MkdirAll(filepath.Join(base, "input"), 0755)) require.NoError(t, os.MkdirAll(filepath.Join(base, "output"), 0755)) require.NoError(t, os.WriteFile(filepath.Join(base, "input", "1.txt"), []byte("in1"), 0644)) diff --git a/package/filestorage/validation_rules.go b/package/filestorage/validation_rules.go index a3420a7e..712688a5 100644 --- a/package/filestorage/validation_rules.go +++ b/package/filestorage/validation_rules.go @@ -69,7 +69,7 @@ func (r *NonEmptyArchiveRule) Validate(ctx ValidationContext) error { Message: "failed to read decompressed archive directory", Cause: err, Context: map[string]interface{}{ - "folder_path": ctx.FolderPath, + contextKeyFolderPath: ctx.FolderPath, }, } } @@ -79,7 +79,7 @@ func (r *NonEmptyArchiveRule) Validate(ctx ValidationContext) error { Message: "archive is empty or does not contain any files", Cause: nil, Context: map[string]interface{}{ - "folder_path": ctx.FolderPath, + contextKeyFolderPath: ctx.FolderPath, }, } } @@ -103,7 +103,7 @@ func (r *RequiredEntriesRule) Validate(ctx ValidationContext) error { Message: "failed to read directory", Cause: err, Context: map[string]interface{}{ - "folder_path": ctx.FolderPath, + contextKeyFolderPath: ctx.FolderPath, }, } } @@ -174,8 +174,8 @@ func (r *DirectoryFilesRule) Validate(ctx ValidationContext) error { Message: fmt.Sprintf("failed to read %s directory in the archive", r.Config.Name), Cause: err, Context: map[string]interface{}{ - "directory_path": dirPath, - "directory_name": r.Config.Name, + contextKeyDirectoryPath: dirPath, + contextKeyDirectoryName: r.Config.Name, }, } } @@ -183,8 +183,8 @@ func (r *DirectoryFilesRule) Validate(ctx ValidationContext) error { for i, file := range dirEntries { if file.IsDir() { context := map[string]interface{}{ - "directory_name": r.Config.Name, - "subdirectory": file.Name(), + contextKeyDirectoryName: r.Config.Name, + "subdirectory": file.Name(), } return &ValidationError{ RuleName: r.Name(), @@ -216,10 +216,10 @@ func (r *DirectoryFilesRule) validateFileExtension(fileName string) error { ext := filepath.Ext(fileName) if !slices.Contains(r.Config.AcceptedExtensions, ext) { context := map[string]interface{}{ - "directory_name": r.Config.Name, - "file_name": fileName, - "file_extension": ext, - "accepted_extensions": r.Config.AcceptedExtensions, + contextKeyDirectoryName: r.Config.Name, + contextKeyFileName: fileName, + "file_extension": ext, + "accepted_extensions": r.Config.AcceptedExtensions, } return &ValidationError{ RuleName: r.Name(), @@ -237,11 +237,11 @@ func (r *DirectoryFilesRule) validateSequentialNaming(fileName string, expectedN base := fileName[:len(fileName)-len(ext)] if base != strconv.Itoa(expectedNumber) { context := map[string]interface{}{ - "directory_name": r.Config.Name, - "file_name": fileName, - "expected_number": expectedNumber, - "actual_base_name": base, - "expected_pattern": fmt.Sprintf("%d%s", expectedNumber, r.Config.AcceptedExtensions[0]), + contextKeyDirectoryName: r.Config.Name, + contextKeyFileName: fileName, + "expected_number": expectedNumber, + "actual_base_name": base, + "expected_pattern": fmt.Sprintf("%d%s", expectedNumber, r.Config.AcceptedExtensions[0]), } return &ValidationError{ RuleName: r.Name(), @@ -264,17 +264,17 @@ func (r *DirectoryFilesRule) validateFileNotEmpty(dirPath, fileName string) erro Message: fmt.Sprintf("failed to get file info for %s file in the archive", r.Config.Name), Cause: err, Context: map[string]interface{}{ - "file_path": filePath, - "directory_name": r.Config.Name, - "file_name": fileName, + contextKeyFilePath: filePath, + contextKeyDirectoryName: r.Config.Name, + contextKeyFileName: fileName, }, } } if fileInfo.Size() == 0 { context := map[string]interface{}{ - "directory_name": r.Config.Name, - "file_name": fileName, - "file_size": fileInfo.Size(), + contextKeyDirectoryName: r.Config.Name, + contextKeyFileName: fileName, + "file_size": fileInfo.Size(), } return &ValidationError{ RuleName: r.Name(), diff --git a/package/filestorage/validation_rules_test.go b/package/filestorage/validation_rules_test.go index 05426f92..ebc6cd55 100644 --- a/package/filestorage/validation_rules_test.go +++ b/package/filestorage/validation_rules_test.go @@ -16,15 +16,15 @@ func setupTestDirectory(t *testing.T) string { tempDir := t.TempDir() // Create input directory with valid files - inputDir := filepath.Join(tempDir, "input") + inputDir := filepath.Join(tempDir, inputDirectoryName) require.NoError(t, os.MkdirAll(inputDir, 0755)) // Create output directory with valid files - outputDir := filepath.Join(tempDir, "output") + outputDir := filepath.Join(tempDir, outputDirectoryName) require.NoError(t, os.MkdirAll(outputDir, 0755)) // Create description.pdf - descPath := filepath.Join(tempDir, "description.pdf") + descPath := filepath.Join(tempDir, descriptionFilename) require.NoError(t, os.WriteFile(descPath, []byte("test description"), 0644)) // Create input files @@ -57,7 +57,7 @@ func TestArchiveValidatorAddRule(t *testing.T) { t.Run("Add multiple rules", func(t *testing.T) { validator := NewArchiveValidator() validator.AddRule(&NonEmptyArchiveRule{}) - validator.AddRule(&RequiredEntriesRule{RequiredEntries: []string{"input", "output"}}) + validator.AddRule(&RequiredEntriesRule{RequiredEntries: []string{inputDirectoryName, outputDirectoryName}}) assert.NotNil(t, validator) }) } @@ -70,7 +70,7 @@ func TestArchiveValidatorValidate(t *testing.T) { validator.AddRule(&NonEmptyArchiveRule{}) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -86,7 +86,7 @@ func TestArchiveValidatorValidate(t *testing.T) { validator.AddRule(&NonEmptyArchiveRule{}) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -104,7 +104,7 @@ func TestArchiveValidatorValidate(t *testing.T) { validator := NewArchiveValidator() ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -118,10 +118,10 @@ func TestArchiveValidatorValidate(t *testing.T) { validator := NewArchiveValidator() validator.AddRule(&NonEmptyArchiveRule{}) - validator.AddRule(&RequiredEntriesRule{RequiredEntries: []string{"input", "output"}}) + validator.AddRule(&RequiredEntriesRule{RequiredEntries: []string{inputDirectoryName, outputDirectoryName}}) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -145,7 +145,7 @@ func TestNonEmptyArchiveRule(t *testing.T) { tempDir := setupTestDirectory(t) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -157,7 +157,7 @@ func TestNonEmptyArchiveRule(t *testing.T) { tempDir := t.TempDir() ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -172,7 +172,7 @@ func TestNonEmptyArchiveRule(t *testing.T) { t.Run("Validate fails with invalid path", func(t *testing.T) { ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: "/nonexistent/path", } @@ -188,7 +188,7 @@ func TestNonEmptyArchiveRule(t *testing.T) { func TestRequiredEntriesRule(t *testing.T) { t.Run("Name returns correct value", func(t *testing.T) { - rule := &RequiredEntriesRule{RequiredEntries: []string{"input", "output"}} + rule := &RequiredEntriesRule{RequiredEntries: []string{inputDirectoryName, outputDirectoryName}} assert.Equal(t, "required-entries", rule.Name()) }) @@ -196,11 +196,11 @@ func TestRequiredEntriesRule(t *testing.T) { tempDir := setupTestDirectory(t) rule := &RequiredEntriesRule{ - RequiredEntries: []string{"input", "output", "description.pdf"}, + RequiredEntries: []string{inputDirectoryName, outputDirectoryName, descriptionFilename}, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -210,14 +210,14 @@ func TestRequiredEntriesRule(t *testing.T) { t.Run("Validate fails with missing entry", func(t *testing.T) { tempDir := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(tempDir, "input"), 0755)) + require.NoError(t, os.MkdirAll(filepath.Join(tempDir, inputDirectoryName), 0755)) rule := &RequiredEntriesRule{ - RequiredEntries: []string{"input", "output"}, + RequiredEntries: []string{inputDirectoryName, outputDirectoryName}, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -235,11 +235,11 @@ func TestRequiredEntriesRule(t *testing.T) { require.NoError(t, os.WriteFile(filepath.Join(tempDir, "extra.txt"), []byte("extra"), 0644)) rule := &RequiredEntriesRule{ - RequiredEntries: []string{"input", "output", "description.pdf"}, + RequiredEntries: []string{inputDirectoryName, outputDirectoryName, descriptionFilename}, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -253,10 +253,10 @@ func TestRequiredEntriesRule(t *testing.T) { }) t.Run("Validate fails with invalid path", func(t *testing.T) { - rule := &RequiredEntriesRule{RequiredEntries: []string{"input", "output"}} + rule := &RequiredEntriesRule{RequiredEntries: []string{inputDirectoryName, outputDirectoryName}} ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: "/nonexistent/path", } @@ -274,8 +274,8 @@ func TestDirectoryFilesRule(t *testing.T) { t.Run("Name returns correct value with directory name", func(t *testing.T) { rule := &DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt}, }, } assert.Equal(t, "directory-files-input", rule.Name()) @@ -286,14 +286,14 @@ func TestDirectoryFilesRule(t *testing.T) { rule := &DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt}, RequireSequential: true, }, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -303,19 +303,19 @@ func TestDirectoryFilesRule(t *testing.T) { t.Run("Validate fails with subdirectory in directory", func(t *testing.T) { tempDir := t.TempDir() - inputDir := filepath.Join(tempDir, "input") + inputDir := filepath.Join(tempDir, inputDirectoryName) require.NoError(t, os.MkdirAll(inputDir, 0755)) require.NoError(t, os.MkdirAll(filepath.Join(inputDir, "subdir"), 0755)) rule := &DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt}, }, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -329,19 +329,19 @@ func TestDirectoryFilesRule(t *testing.T) { t.Run("Validate fails with wrong extension", func(t *testing.T) { tempDir := t.TempDir() - inputDir := filepath.Join(tempDir, "input") + inputDir := filepath.Join(tempDir, inputDirectoryName) require.NoError(t, os.MkdirAll(inputDir, 0755)) require.NoError(t, os.WriteFile(filepath.Join(inputDir, "1.json"), []byte("test"), 0644)) rule := &DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt", ".in"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt, ".in"}, }, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -355,21 +355,21 @@ func TestDirectoryFilesRule(t *testing.T) { t.Run("Validate fails with non-sequential naming", func(t *testing.T) { tempDir := t.TempDir() - inputDir := filepath.Join(tempDir, "input") + inputDir := filepath.Join(tempDir, inputDirectoryName) require.NoError(t, os.MkdirAll(inputDir, 0755)) require.NoError(t, os.WriteFile(filepath.Join(inputDir, "1.txt"), []byte("test"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(inputDir, "3.txt"), []byte("test"), 0644)) // Skipped 2 rule := &DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt}, RequireSequential: true, }, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -383,20 +383,20 @@ func TestDirectoryFilesRule(t *testing.T) { t.Run("Validate fails with empty file", func(t *testing.T) { tempDir := t.TempDir() - inputDir := filepath.Join(tempDir, "input") + inputDir := filepath.Join(tempDir, inputDirectoryName) require.NoError(t, os.MkdirAll(inputDir, 0755)) require.NoError(t, os.WriteFile(filepath.Join(inputDir, "1.txt"), []byte{}, 0644)) // Empty file rule := &DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt}, RequireSequential: true, }, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -413,13 +413,13 @@ func TestDirectoryFilesRule(t *testing.T) { rule := &DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt}, }, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -433,21 +433,21 @@ func TestDirectoryFilesRule(t *testing.T) { t.Run("Validate success without sequential requirement", func(t *testing.T) { tempDir := t.TempDir() - inputDir := filepath.Join(tempDir, "input") + inputDir := filepath.Join(tempDir, inputDirectoryName) require.NoError(t, os.MkdirAll(inputDir, 0755)) require.NoError(t, os.WriteFile(filepath.Join(inputDir, "test1.txt"), []byte("test"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(inputDir, "test2.txt"), []byte("test"), 0644)) rule := &DirectoryFilesRule{ Config: DirectoryConfig{ - Name: "input", - AcceptedExtensions: []string{".txt"}, + Name: inputDirectoryName, + AcceptedExtensions: []string{acceptedTextExt}, RequireSequential: false, }, } ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -467,7 +467,7 @@ func TestInputOutputMatchRule(t *testing.T) { tempDir := setupTestDirectory(t) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -477,8 +477,8 @@ func TestInputOutputMatchRule(t *testing.T) { t.Run("Validate fails with mismatched counts", func(t *testing.T) { tempDir := t.TempDir() - inputDir := filepath.Join(tempDir, "input") - outputDir := filepath.Join(tempDir, "output") + inputDir := filepath.Join(tempDir, inputDirectoryName) + outputDir := filepath.Join(tempDir, outputDirectoryName) require.NoError(t, os.MkdirAll(inputDir, 0755)) require.NoError(t, os.MkdirAll(outputDir, 0755)) @@ -490,7 +490,7 @@ func TestInputOutputMatchRule(t *testing.T) { require.NoError(t, os.WriteFile(filepath.Join(outputDir, "1.txt"), []byte("output 1"), 0644)) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -505,11 +505,11 @@ func TestInputOutputMatchRule(t *testing.T) { t.Run("Validate fails with empty directories", func(t *testing.T) { tempDir := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(tempDir, "input"), 0755)) - require.NoError(t, os.MkdirAll(filepath.Join(tempDir, "output"), 0755)) + require.NoError(t, os.MkdirAll(filepath.Join(tempDir, inputDirectoryName), 0755)) + require.NoError(t, os.MkdirAll(filepath.Join(tempDir, outputDirectoryName), 0755)) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -523,10 +523,10 @@ func TestInputOutputMatchRule(t *testing.T) { t.Run("Validate fails with missing input directory", func(t *testing.T) { tempDir := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(tempDir, "output"), 0755)) + require.NoError(t, os.MkdirAll(filepath.Join(tempDir, outputDirectoryName), 0755)) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -535,17 +535,17 @@ func TestInputOutputMatchRule(t *testing.T) { var validationErr *ValidationError require.ErrorAs(t, err, &validationErr) - assert.Contains(t, validationErr.Message, "input") + assert.Contains(t, validationErr.Message, inputDirectoryName) }) t.Run("Validate fails with missing output directory", func(t *testing.T) { tempDir := t.TempDir() - inputDir := filepath.Join(tempDir, "input") + inputDir := filepath.Join(tempDir, inputDirectoryName) require.NoError(t, os.MkdirAll(inputDir, 0755)) require.NoError(t, os.WriteFile(filepath.Join(inputDir, "1.txt"), []byte("input 1"), 0644)) ctx := ValidationContext{ - ArchivePath: "test.zip", + ArchivePath: testZipFilename, FolderPath: tempDir, } @@ -554,6 +554,6 @@ func TestInputOutputMatchRule(t *testing.T) { var validationErr *ValidationError require.ErrorAs(t, err, &validationErr) - assert.Contains(t, validationErr.Message, "output") + assert.Contains(t, validationErr.Message, outputDirectoryName) }) } diff --git a/package/service/access_control_service_test.go b/package/service/access_control_service_test.go index c9b30d7c..e07bf099 100644 --- a/package/service/access_control_service_test.go +++ b/package/service/access_control_service_test.go @@ -383,7 +383,7 @@ func TestGetCollaborators(t *testing.T) { UserID: 10, Permission: types.PermissionEdit, BaseModel: models.BaseModel{CreatedAt: now}, - User: models.User{ID: 10, Name: "Test User", Email: "test@example.com"}, + User: models.User{ID: 10, Name: testUserName, Email: "test@example.com"}, }, { ResourceType: resourceType, @@ -400,7 +400,7 @@ func TestGetCollaborators(t *testing.T) { require.NoError(t, err) assert.Len(t, collaborators, 2) assert.Equal(t, int64(10), collaborators[0].UserID) - assert.Equal(t, "Test User", collaborators[0].UserName) + assert.Equal(t, testUserName, collaborators[0].UserName) assert.Equal(t, "test@example.com", collaborators[0].UserEmail) assert.Equal(t, types.PermissionEdit, collaborators[0].Permission) }) diff --git a/package/service/auth_service_test.go b/package/service/auth_service_test.go index 6cb49acc..26a2c5a1 100644 --- a/package/service/auth_service_test.go +++ b/package/service/auth_service_test.go @@ -18,6 +18,13 @@ import ( "gorm.io/gorm" ) +const ( + testFirstName = "name" + testUserSurname = "surname" + testPassword = "Password123!" + testUserEmail = "email5@email.com" +) + func TestRegister(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -30,19 +37,19 @@ func TestRegister(t *testing.T) { t.Run("get user by email when user exists", func(t *testing.T) { ur.EXPECT().GetByEmail(db, "email2@email.com").Return(&models.User{ ID: 1, - Name: "name", - Surname: "surname", + Name: testFirstName, + Surname: testUserSurname, Email: "email2@email.com", Username: "username2", - PasswordHash: "password", + PasswordHash: testPasswordHash, }, nil).Times(1) userRegister := schemas.UserRegisterRequest{ - Name: "name", - Surname: "surname", + Name: testFirstName, + Surname: testUserSurname, Email: "email2@email.com", Username: "username", - Password: "Password123!", + Password: testPassword, } response, err := as.Register(db, userRegister) require.ErrorIs(t, err, errors.ErrUserAlreadyExists) @@ -58,11 +65,11 @@ func TestRegister(t *testing.T) { }, nil).Times(1) userRegister := schemas.UserRegisterRequest{ - Name: "name", - Surname: "surname", + Name: testFirstName, + Surname: testUserSurname, Email: "email3@email.com", Username: "username3", - Password: "Password123!", + Password: testPassword, } response, err := as.Register(db, userRegister) require.NoError(t, err) @@ -75,11 +82,11 @@ func TestRegister(t *testing.T) { t.Run("unexpected repository error", func(t *testing.T) { ur.EXPECT().GetByEmail(db, "email4@email.com").Return(nil, gorm.ErrInvalidDB).Times(1) userRegister := schemas.UserRegisterRequest{ - Name: "name", - Surname: "surname", + Name: testFirstName, + Surname: testUserSurname, Email: "email4@email.com", Username: "username4", - Password: "Password123!", + Password: testPassword, } response, err := as.Register(db, userRegister) require.ErrorIs(t, err, gorm.ErrInvalidDB) @@ -87,15 +94,15 @@ func TestRegister(t *testing.T) { }) t.Run("failed to create user", func(t *testing.T) { - ur.EXPECT().GetByEmail(db, "email5@email.com").Return(nil, gorm.ErrRecordNotFound).Times(1) + ur.EXPECT().GetByEmail(db, testUserEmail).Return(nil, gorm.ErrRecordNotFound).Times(1) ur.EXPECT().Create(db, gomock.Any()).Return(int64(0), gorm.ErrInvalidDB).Times(1) userRegister := schemas.UserRegisterRequest{ - Name: "name", - Surname: "surname", - Email: "email5@email.com", + Name: testFirstName, + Surname: testUserSurname, + Email: testUserEmail, Username: "username5", - Password: "Password123!", + Password: testPassword, } response, err := as.Register(db, userRegister) require.ErrorIs(t, err, gorm.ErrInvalidDB) @@ -112,15 +119,15 @@ func TestLogin(t *testing.T) { as := service.NewAuthService(ur, js) db := &testutils.MockDatabase{} - password := "Password123!" + password := testPassword hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) require.NoError(t, err) user := &models.User{ ID: 1, - Name: "name", - Surname: "surname", - Email: "email5@email.com", + Name: testFirstName, + Surname: testUserSurname, + Email: testUserEmail, Username: "username", PasswordHash: string(hash), } @@ -130,7 +137,7 @@ func TestLogin(t *testing.T) { userLogin := schemas.UserLoginRequest{ Email: "nonexistent@email.com", - Password: "password", + Password: testPasswordHash, } response, err := as.Login(db, userLogin) @@ -139,10 +146,10 @@ func TestLogin(t *testing.T) { }) t.Run("compare password hash fails", func(t *testing.T) { - ur.EXPECT().GetByEmail(db, "email5@email.com").Return(user, nil).Times(1) + ur.EXPECT().GetByEmail(db, testUserEmail).Return(user, nil).Times(1) userLogin := schemas.UserLoginRequest{ - Email: "email5@email.com", + Email: testUserEmail, Password: "wrongpassword", } @@ -152,14 +159,14 @@ func TestLogin(t *testing.T) { }) t.Run("successful user login", func(t *testing.T) { - ur.EXPECT().GetByEmail(db, "email5@email.com").Return(user, nil).Times(1) + ur.EXPECT().GetByEmail(db, testUserEmail).Return(user, nil).Times(1) js.EXPECT().GenerateTokens(db, user.ID).Return(&schemas.JWTTokens{ AccessToken: "access-token", RefreshToken: "refresh-token", }, nil).Times(1) userLogin := schemas.UserLoginRequest{ - Email: "email5@email.com", + Email: testUserEmail, Password: password, } diff --git a/package/service/contest_service_test.go b/package/service/contest_service_test.go index 9d3afc70..09e6482d 100644 --- a/package/service/contest_service_test.go +++ b/package/service/contest_service_test.go @@ -20,6 +20,13 @@ import ( "gorm.io/gorm" ) +const ( + testContestName = "Test Contest" + testSortByStartTime = "start_time" + testCreatorName = "Creator" + testOngoingTaskTitle = "Ongoing Task" +) + func TestContestService_GetMyContestResults(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -205,7 +212,7 @@ func TestContestWithStatsToSchema(t *testing.T) { contestWithStats := &models.ContestWithStats{ Contest: models.Contest{ ID: 1, - Name: "Test Contest", + Name: testContestName, Description: "Test Description", StartAt: startTime, EndAt: &endTime, @@ -222,7 +229,7 @@ func TestContestWithStatsToSchema(t *testing.T) { assert.NotNil(t, result) assert.Equal(t, int64(1), result.ID) - assert.Equal(t, "Test Contest", result.Name) + assert.Equal(t, testContestName, result.Name) assert.Equal(t, "Test Description", result.Description) assert.Equal(t, startTime, result.StartAt) assert.Equal(t, &endTime, result.EndAt) @@ -239,7 +246,7 @@ func TestContestWithStatsToSchemaWithNilUserInfo(t *testing.T) { contestWithStats := &models.ContestWithStats{ Contest: models.Contest{ ID: 1, - Name: "Test Contest", + Name: testContestName, Description: "Test Description", StartAt: startTime, EndAt: &endTime, @@ -256,7 +263,7 @@ func TestContestWithStatsToSchemaWithNilUserInfo(t *testing.T) { assert.NotNil(t, result) assert.Equal(t, int64(1), result.ID) - assert.Equal(t, "Test Contest", result.Name) + assert.Equal(t, testContestName, result.Name) assert.Equal(t, "Test Description", result.Description) assert.Equal(t, startTime, result.StartAt) assert.Equal(t, &endTime, result.EndAt) @@ -343,7 +350,7 @@ func TestContestService_GetPastContests(t *testing.T) { queryParams := schemas.PaginationParams{ Limit: 10, Offset: 0, - Sort: "start_time", + Sort: testSortByStartTime, } visible := true @@ -359,7 +366,7 @@ func TestContestService_GetPastContests(t *testing.T) { }, } - cr.EXPECT().GetPastContestsWithStats(db, currentUser.ID, 0, 10, "start_time").Return(contestsWithStats, int64(1), nil).Times(1) + cr.EXPECT().GetPastContestsWithStats(db, currentUser.ID, 0, 10, testSortByStartTime).Return(contestsWithStats, int64(1), nil).Times(1) result, err := cs.GetPastContests(db, currentUser, queryParams) @@ -379,10 +386,10 @@ func TestContestService_GetPastContests(t *testing.T) { queryParams := schemas.PaginationParams{ Limit: 10, Offset: 0, - Sort: "start_time", + Sort: testSortByStartTime, } - cr.EXPECT().GetPastContestsWithStats(db, currentUser.ID, 0, 10, "start_time").Return(nil, int64(0), errors.ErrDatabaseConnection).Times(1) + cr.EXPECT().GetPastContestsWithStats(db, currentUser.ID, 0, 10, testSortByStartTime).Return(nil, int64(0), errors.ErrDatabaseConnection).Times(1) result, err := cs.GetPastContests(db, currentUser, queryParams) @@ -414,7 +421,7 @@ func TestContestService_GetUpcomingContests(t *testing.T) { queryParams := schemas.PaginationParams{ Limit: 10, Offset: 0, - Sort: "start_time", + Sort: testSortByStartTime, } visible := true @@ -430,7 +437,7 @@ func TestContestService_GetUpcomingContests(t *testing.T) { }, } - cr.EXPECT().GetUpcomingContestsWithStats(db, currentUser.ID, 0, 10, "start_time").Return(contestsWithStats, int64(1), nil).Times(1) + cr.EXPECT().GetUpcomingContestsWithStats(db, currentUser.ID, 0, 10, testSortByStartTime).Return(contestsWithStats, int64(1), nil).Times(1) result, err := cs.GetUpcomingContests(db, currentUser, queryParams) @@ -450,10 +457,10 @@ func TestContestService_GetUpcomingContests(t *testing.T) { queryParams := schemas.PaginationParams{ Limit: 10, Offset: 0, - Sort: "start_time", + Sort: testSortByStartTime, } - cr.EXPECT().GetUpcomingContestsWithStats(db, currentUser.ID, 0, 10, "start_time").Return(nil, int64(0), errors.ErrDatabaseConnection).Times(1) + cr.EXPECT().GetUpcomingContestsWithStats(db, currentUser.ID, 0, 10, testSortByStartTime).Return(nil, int64(0), errors.ErrDatabaseConnection).Times(1) result, err := cs.GetUpcomingContests(db, currentUser, queryParams) @@ -487,13 +494,13 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -526,13 +533,13 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, // Same as current user } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -618,7 +625,7 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } @@ -642,13 +649,13 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -681,13 +688,13 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } cr.EXPECT().Get(db, contestID).Return(contest, nil).Times(1) @@ -712,13 +719,13 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } cr.EXPECT().Get(db, contestID).Return(contest, nil).Times(1) @@ -742,13 +749,13 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } cr.EXPECT().Get(db, contestID).Return(contest, nil).Times(1) @@ -773,13 +780,13 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -812,13 +819,13 @@ func TestContestService_ApproveRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -867,13 +874,13 @@ func TestContestService_RejectRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -905,13 +912,13 @@ func TestContestService_RejectRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -996,7 +1003,7 @@ func TestContestService_RejectRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } @@ -1020,13 +1027,13 @@ func TestContestService_RejectRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -1059,13 +1066,13 @@ func TestContestService_RejectRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } cr.EXPECT().Get(db, contestID).Return(contest, nil).Times(1) @@ -1090,13 +1097,13 @@ func TestContestService_RejectRegistrationRequest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, } user := &models.User{ ID: userID, - Username: "testuser", + Username: testUsername, } request := &models.ContestRegistrationRequests{ @@ -1145,7 +1152,7 @@ func TestContestService_GetDetailed(t *testing.T) { contest := &repository.ContestDetailed{ Contest: models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, }, @@ -1158,7 +1165,7 @@ func TestContestService_GetDetailed(t *testing.T) { require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, contestID, result.ID) - assert.Equal(t, "Test Contest", result.Name) + assert.Equal(t, testContestName, result.Name) }) t.Run("successful retrieval - invisible contest by admin", func(t *testing.T) { @@ -1172,7 +1179,7 @@ func TestContestService_GetDetailed(t *testing.T) { contest := &repository.ContestDetailed{ Contest: models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, }, @@ -1200,7 +1207,7 @@ func TestContestService_GetDetailed(t *testing.T) { contest := &repository.ContestDetailed{ Contest: models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, }, @@ -1228,7 +1235,7 @@ func TestContestService_GetDetailed(t *testing.T) { contest := &repository.ContestDetailed{ Contest: models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, }, @@ -1257,7 +1264,7 @@ func TestContestService_GetDetailed(t *testing.T) { contest := &repository.ContestDetailed{ Contest: models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, }, @@ -1333,7 +1340,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, } @@ -1349,9 +1356,9 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { TaskID: 1, Task: models.Task{ ID: 1, - Title: "Test Task", + Title: testTaskTitle, Author: models.User{ - Name: "Creator", + Name: testCreatorName, }, }, StartAt: time.Now().Add(-1 * time.Hour), @@ -1367,8 +1374,8 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { require.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, int64(1), result[0].Task.ID) - assert.Equal(t, "Test Task", result[0].Task.Title) - assert.Equal(t, "Creator", result[0].CreatorName) + assert.Equal(t, testTaskTitle, result[0].Task.Title) + assert.Equal(t, testCreatorName, result[0].CreatorName) }) t.Run("successful retrieval - user with edit permission", func(t *testing.T) { @@ -1380,7 +1387,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, } @@ -1396,9 +1403,9 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { TaskID: 1, Task: models.Task{ ID: 1, - Title: "Test Task", + Title: testTaskTitle, Author: models.User{ - Name: "Creator", + Name: testCreatorName, }, }, StartAt: time.Now().Add(-1 * time.Hour), @@ -1424,7 +1431,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, } @@ -1451,7 +1458,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, } @@ -1468,9 +1475,9 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { TaskID: 1, Task: models.Task{ ID: 1, - Title: "Ongoing Task", + Title: testOngoingTaskTitle, Author: models.User{ - Name: "Creator", + Name: testCreatorName, }, }, StartAt: now.Add(-2 * time.Hour), @@ -1484,7 +1491,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { ID: 2, Title: "Past Task", Author: models.User{ - Name: "Creator", + Name: testCreatorName, }, }, StartAt: now.Add(-3 * time.Hour), @@ -1499,7 +1506,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { require.NoError(t, err) assert.Len(t, result, 1) // Only ongoing task - assert.Equal(t, "Ongoing Task", result[0].Task.Title) + assert.Equal(t, testOngoingTaskTitle, result[0].Task.Title) }) t.Run("filter by status - past", func(t *testing.T) { @@ -1512,7 +1519,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, } @@ -1529,9 +1536,9 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { TaskID: 1, Task: models.Task{ ID: 1, - Title: "Ongoing Task", + Title: testOngoingTaskTitle, Author: models.User{ - Name: "Creator", + Name: testCreatorName, }, }, StartAt: now.Add(-2 * time.Hour), @@ -1545,7 +1552,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { ID: 2, Title: "Past Task", Author: models.User{ - Name: "Creator", + Name: testCreatorName, }, }, StartAt: now.Add(-3 * time.Hour), @@ -1573,7 +1580,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, } @@ -1592,7 +1599,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { ID: 1, Title: "Upcoming Task", Author: models.User{ - Name: "Creator", + Name: testCreatorName, }, }, StartAt: futureStart, @@ -1604,9 +1611,9 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { TaskID: 2, Task: models.Task{ ID: 2, - Title: "Ongoing Task", + Title: testOngoingTaskTitle, Author: models.User{ - Name: "Creator", + Name: testCreatorName, }, }, StartAt: now.Add(-1 * time.Hour), @@ -1633,7 +1640,7 @@ func TestContestService_GetVisibleTasksForContest(t *testing.T) { contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, CreatedBy: 2, IsVisible: visible, } diff --git a/package/service/group_service_test.go b/package/service/group_service_test.go index 87864099..d264ca4e 100644 --- a/package/service/group_service_test.go +++ b/package/service/group_service_test.go @@ -18,6 +18,8 @@ import ( "go.uber.org/mock/gomock" ) +const testGroupName = "Test Group" + func TestCreateGroup(t *testing.T) { db := &testutils.MockDatabase{} ctrl := gomock.NewController(t) @@ -31,7 +33,7 @@ func TestCreateGroup(t *testing.T) { gr.EXPECT().Create(gomock.Any(), gomock.Any()).Return(int64(1), nil).Times(1) acs.EXPECT().GrantOwnerAccess(gomock.Any(), types.ResourceTypeGroup, int64(1), currentUser.ID).Return(nil).Times(1) groupID, err := gs.Create(db, *currentUser, &schemas.Group{ - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, }) require.NoError(t, err) @@ -41,7 +43,7 @@ func TestCreateGroup(t *testing.T) { t.Run("Not authorized", func(t *testing.T) { currentUser := &schemas.User{ID: 2, Role: types.UserRoleStudent} groupID, err := gs.Create(db, *currentUser, &schemas.Group{ - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, }) require.ErrorIs(t, err, errors.ErrForbidden) @@ -61,7 +63,7 @@ func TestDeleteGroup(t *testing.T) { currentUser := &schemas.User{ID: 1, Role: types.UserRoleAdmin} group := &models.Group{ ID: int64(1), - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, } gr.EXPECT().Get(gomock.Any(), group.ID).Return(group, nil).Times(1) @@ -74,7 +76,7 @@ func TestDeleteGroup(t *testing.T) { currentUser := &schemas.User{ID: 2, Role: types.UserRoleStudent} gr.EXPECT().Get(gomock.Any(), int64(2)).Return(&models.Group{ ID: int64(2), - Name: "Test Group", + Name: testGroupName, CreatedBy: 1, }, nil).Times(1) acs.EXPECT().CanUserAccess(gomock.Any(), types.ResourceTypeGroup, int64(2), currentUser, types.PermissionOwner).Return(errors.ErrForbidden).Times(1) @@ -86,7 +88,7 @@ func TestDeleteGroup(t *testing.T) { currentUser := &schemas.User{ID: 3, Role: types.UserRoleTeacher} gr.EXPECT().Get(gomock.Any(), int64(2)).Return(&models.Group{ ID: int64(2), - Name: "Test Group", + Name: testGroupName, CreatedBy: 1, }, nil).Times(1) acs.EXPECT().CanUserAccess(gomock.Any(), types.ResourceTypeGroup, int64(2), currentUser, types.PermissionOwner).Return(errors.ErrForbidden).Times(1) @@ -103,7 +105,7 @@ func TestGetAllGroup(t *testing.T) { acs := mock_service.NewMockAccessControlService(ctrl) gs := service.NewGroupService(gr, ur, service.NewUserService(ur, mock_service.NewMockContestService(ctrl)), acs) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "id:asc"} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: sortIDAsc} t.Run("No groups", func(t *testing.T) { currentUser := &schemas.User{ID: 1, Role: types.UserRoleAdmin} gr.EXPECT().GetAll(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]models.Group{}, nil).Times(1) @@ -117,7 +119,7 @@ func TestGetAllGroup(t *testing.T) { gr.EXPECT().GetAll(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]models.Group{ { ID: 1, - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, }, }, nil).Times(1) @@ -144,7 +146,7 @@ func TestGetAllGroup(t *testing.T) { ).Return([]models.Group{ { ID: 1, - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, }, }, nil).Times(1) @@ -166,19 +168,19 @@ func TestGetGroup(t *testing.T) { currentUser := &schemas.User{ID: 1, Role: types.UserRoleAdmin} gr.EXPECT().Get(gomock.Any(), int64(1)).Return(&models.Group{ ID: 1, - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, }, nil).Times(1) group, err := gs.Get(db, *currentUser, 1) require.NoError(t, err) - assert.Equal(t, "Test Group", group.Name) + assert.Equal(t, testGroupName, group.Name) }) t.Run("Not authorized", func(t *testing.T) { currentUser := &schemas.User{ID: 2, Role: types.UserRoleStudent} gr.EXPECT().Get(gomock.Any(), int64(1)).Return(&models.Group{ ID: 1, - Name: "Test Group", + Name: testGroupName, CreatedBy: 3, }, nil).Times(1) acs.EXPECT().CanUserAccess(gomock.Any(), types.ResourceTypeGroup, int64(1), currentUser, types.PermissionEdit).Return(errors.ErrForbidden).Times(1) @@ -216,7 +218,7 @@ func TestAddUsersToGroup(t *testing.T) { user := &schemas.User{ID: 3} gr.EXPECT().Get(gomock.Any(), groupID).Return(&models.Group{ ID: groupID, - Name: "Test Group", + Name: testGroupName, CreatedBy: 4, }, nil).Times(1) acs.EXPECT().CanUserAccess(gomock.Any(), types.ResourceTypeGroup, groupID, currentUser, types.PermissionEdit).Return(errors.ErrForbidden).Times(1) @@ -238,7 +240,7 @@ func TestGetGroupUsers(t *testing.T) { groupID := int64(1) gr.EXPECT().Get(gomock.Any(), groupID).Return(&models.Group{ ID: groupID, - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, }, nil).Times(1) user := &schemas.User{ID: int64(2)} @@ -253,7 +255,7 @@ func TestGetGroupUsers(t *testing.T) { groupID := int64(1) gr.EXPECT().Get(gomock.Any(), groupID).Return(&models.Group{ ID: groupID, - Name: "Test Group", + Name: testGroupName, CreatedBy: 4, }, nil).Times(1) acs.EXPECT().CanUserAccess(gomock.Any(), types.ResourceTypeGroup, groupID, currentUser, types.PermissionEdit).Return(errors.ErrForbidden).Times(1) @@ -276,7 +278,7 @@ func TestEditGroup(t *testing.T) { currentUser := &schemas.User{ID: 1, Role: types.UserRoleAdmin} group := &models.Group{ ID: int64(1), - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, } gr.EXPECT().Get(gomock.Any(), group.ID).Return(group, nil).Times(1) @@ -310,7 +312,7 @@ func TestEditGroup(t *testing.T) { currentUser := &schemas.User{ID: 3, Role: types.UserRoleTeacher} group := &models.Group{ ID: int64(1), - Name: "Test Group", + Name: testGroupName, CreatedBy: 1, // Assuming the admin user ID is 1 } gr.EXPECT().Get(gomock.Any(), group.ID).Return(group, nil).Times(1) @@ -349,7 +351,7 @@ func TestDeleteUsersFromGroup(t *testing.T) { groupID := int64(1) gr.EXPECT().Get(gomock.Any(), groupID).Return(&models.Group{ ID: groupID, - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, }, nil).Times(1) user := &schemas.User{ID: 2} @@ -365,7 +367,7 @@ func TestDeleteUsersFromGroup(t *testing.T) { groupID := int64(1) // Assuming the group ID is 1 for the test gr.EXPECT().Get(gomock.Any(), groupID).Return(&models.Group{ ID: groupID, - Name: "Test Group", + Name: testGroupName, CreatedBy: 4, }, nil).Times(1) acs.EXPECT().CanUserAccess(gomock.Any(), types.ResourceTypeGroup, groupID, currentUser, types.PermissionEdit).Return(errors.ErrForbidden).Times(1) @@ -378,7 +380,7 @@ func TestDeleteUsersFromGroup(t *testing.T) { currentUser := &schemas.User{ID: 3, Role: types.UserRoleTeacher} group := &models.Group{ ID: int64(1), - Name: "Test Group", + Name: testGroupName, CreatedBy: 1, // Assuming the admin user ID is 1 } gr.EXPECT().Get(gomock.Any(), group.ID).Return(group, nil).Times(1) @@ -393,7 +395,7 @@ func TestDeleteUsersFromGroup(t *testing.T) { groupID := int64(1) gr.EXPECT().Get(gomock.Any(), groupID).Return(&models.Group{ ID: groupID, - Name: "Test Group", + Name: testGroupName, CreatedBy: currentUser.ID, }, nil).Times(1) ur.EXPECT().Get(gomock.Any(), int64(9999)).Return(nil, errors.ErrUserNotFound).Times(1) diff --git a/package/service/language_service_test.go b/package/service/language_service_test.go index af57c427..d32f78f4 100644 --- a/package/service/language_service_test.go +++ b/package/service/language_service_test.go @@ -14,6 +14,15 @@ import ( "go.uber.org/mock/gomock" ) +const ( + testPythonName = "python" + testPythonVersion310 = "3.10" + testPythonVersion39 = "3.9" + testPythonExtension = ".py" + testJSName = "javascript" + testJSExtension = ".js" +) + var trueValue = true var falseValue = false @@ -33,14 +42,14 @@ func TestLanguageServiceInit(t *testing.T) { Extension string `json:"extension"` }{ { - Name: "python", - Versions: []string{"3.9", "3.10"}, - Extension: ".py", + Name: testPythonName, + Versions: []string{testPythonVersion39, testPythonVersion310}, + Extension: testPythonExtension, }, { - Name: "javascript", + Name: testJSName, Versions: []string{"18", "20"}, - Extension: ".js", + Extension: testJSExtension, }, }, } @@ -51,27 +60,27 @@ func TestLanguageServiceInit(t *testing.T) { // Expect creates for each language-version combination lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "python", - Version: "3.9", - FileExtension: ".py", + Type: testPythonName, + Version: testPythonVersion39, + FileExtension: testPythonExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "python", - Version: "3.10", - FileExtension: ".py", + Type: testPythonName, + Version: testPythonVersion310, + FileExtension: testPythonExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "javascript", + Type: testJSName, Version: "18", - FileExtension: ".js", + FileExtension: testJSExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "javascript", + Type: testJSName, Version: "20", - FileExtension: ".js", + FileExtension: testJSExtension, }).Return(nil).Times(1) err := ls.Init(db, workerLanguages) @@ -80,23 +89,23 @@ func TestLanguageServiceInit(t *testing.T) { t.Run("Success with existing enabled languages", func(t *testing.T) { existingLanguages := []models.LanguageConfig{ - {ID: 1, Type: "python", Version: "3.9", FileExtension: ".py", IsDisabled: &falseValue}, - {ID: 2, Type: "python", Version: "3.10", FileExtension: ".py", IsDisabled: &trueValue}, + {ID: 1, Type: testPythonName, Version: testPythonVersion39, FileExtension: testPythonExtension, IsDisabled: &falseValue}, + {ID: 2, Type: testPythonName, Version: testPythonVersion310, FileExtension: testPythonExtension, IsDisabled: &trueValue}, } lr.EXPECT().GetAll(db).Return(existingLanguages, nil).Times(1) // Expect creates for new language-version combinations lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "javascript", + Type: testJSName, Version: "18", - FileExtension: ".js", + FileExtension: testJSExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "javascript", + Type: testJSName, Version: "20", - FileExtension: ".js", + FileExtension: testJSExtension, }).Return(nil).Times(1) err := ls.Init(db, workerLanguages) @@ -105,7 +114,7 @@ func TestLanguageServiceInit(t *testing.T) { t.Run("Success with languages to disable", func(t *testing.T) { existingLanguages := []models.LanguageConfig{ - {ID: 1, Type: "python", Version: "3.9", FileExtension: ".py", IsDisabled: &falseValue}, + {ID: 1, Type: testPythonName, Version: testPythonVersion39, FileExtension: testPythonExtension, IsDisabled: &falseValue}, {ID: 2, Type: "go", Version: "1.19", FileExtension: ".go", IsDisabled: &falseValue}, // This should be disabled } @@ -113,21 +122,21 @@ func TestLanguageServiceInit(t *testing.T) { // Expect creates for new language-version combinations lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "python", - Version: "3.10", - FileExtension: ".py", + Type: testPythonName, + Version: testPythonVersion310, + FileExtension: testPythonExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "javascript", + Type: testJSName, Version: "18", - FileExtension: ".js", + FileExtension: testJSExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "javascript", + Type: testJSName, Version: "20", - FileExtension: ".js", + FileExtension: testJSExtension, }).Return(nil).Times(1) // Expect disabling of language not in worker languages @@ -149,9 +158,9 @@ func TestLanguageServiceInit(t *testing.T) { lr.EXPECT().GetAll(db).Return([]models.LanguageConfig{}, nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "python", - Version: "3.9", - FileExtension: ".py", + Type: testPythonName, + Version: testPythonVersion39, + FileExtension: testPythonExtension, }).Return(assert.AnError).Times(1) err := ls.Init(db, workerLanguages) @@ -168,27 +177,27 @@ func TestLanguageServiceInit(t *testing.T) { // Expect creates for new language-version combinations lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "python", - Version: "3.9", - FileExtension: ".py", + Type: testPythonName, + Version: testPythonVersion39, + FileExtension: testPythonExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "python", - Version: "3.10", - FileExtension: ".py", + Type: testPythonName, + Version: testPythonVersion310, + FileExtension: testPythonExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "javascript", + Type: testJSName, Version: "18", - FileExtension: ".js", + FileExtension: testJSExtension, }).Return(nil).Times(1) lr.EXPECT().Create(db, &models.LanguageConfig{ - Type: "javascript", + Type: testJSName, Version: "20", - FileExtension: ".js", + FileExtension: testJSExtension, }).Return(nil).Times(1) // Error when marking language as disabled @@ -210,8 +219,8 @@ func TestLanguageServiceGetAll(t *testing.T) { t.Run("Success with languages", func(t *testing.T) { languages := []models.LanguageConfig{ - {ID: 1, Type: "python", Version: "3.9", FileExtension: ".py", IsDisabled: &falseValue}, - {ID: 2, Type: "javascript", Version: "18", FileExtension: ".js", IsDisabled: &falseValue}, + {ID: 1, Type: testPythonName, Version: testPythonVersion39, FileExtension: testPythonExtension, IsDisabled: &falseValue}, + {ID: 2, Type: testJSName, Version: "18", FileExtension: testJSExtension, IsDisabled: &falseValue}, } lr.EXPECT().GetAll(db).Return(languages, nil).Times(1) @@ -220,13 +229,13 @@ func TestLanguageServiceGetAll(t *testing.T) { require.NoError(t, err) assert.Len(t, result, 2) assert.Equal(t, int64(1), result[0].ID) - assert.Equal(t, "python", result[0].Type) - assert.Equal(t, "3.9", result[0].Version) - assert.Equal(t, ".py", result[0].FileExtension) + assert.Equal(t, testPythonName, result[0].Type) + assert.Equal(t, testPythonVersion39, result[0].Version) + assert.Equal(t, testPythonExtension, result[0].FileExtension) assert.Equal(t, int64(2), result[1].ID) - assert.Equal(t, "javascript", result[1].Type) + assert.Equal(t, testJSName, result[1].Type) assert.Equal(t, "18", result[1].Version) - assert.Equal(t, ".js", result[1].FileExtension) + assert.Equal(t, testJSExtension, result[1].FileExtension) }) t.Run("Success with no languages", func(t *testing.T) { @@ -257,7 +266,7 @@ func TestLanguageServiceGetAllEnabled(t *testing.T) { t.Run("Success with enabled languages", func(t *testing.T) { languages := []models.LanguageConfig{ - {ID: 1, Type: "python", Version: "3.9", FileExtension: ".py", IsDisabled: &falseValue}, + {ID: 1, Type: testPythonName, Version: testPythonVersion39, FileExtension: testPythonExtension, IsDisabled: &falseValue}, {ID: 3, Type: "java", Version: "17", FileExtension: ".java", IsDisabled: &falseValue}, } @@ -267,9 +276,9 @@ func TestLanguageServiceGetAllEnabled(t *testing.T) { require.NoError(t, err) assert.Len(t, result, 2) assert.Equal(t, int64(1), result[0].ID) - assert.Equal(t, "python", result[0].Type) - assert.Equal(t, "3.9", result[0].Version) - assert.Equal(t, ".py", result[0].FileExtension) + assert.Equal(t, testPythonName, result[0].Type) + assert.Equal(t, testPythonVersion39, result[0].Version) + assert.Equal(t, testPythonExtension, result[0].FileExtension) assert.Equal(t, int64(3), result[1].ID) assert.Equal(t, "java", result[1].Type) assert.Equal(t, "17", result[1].Version) @@ -298,18 +307,18 @@ func TestLanguageToSchema(t *testing.T) { t.Run("Convert model to schema", func(t *testing.T) { language := &models.LanguageConfig{ ID: 1, - Type: "python", - Version: "3.9", - FileExtension: ".py", + Type: testPythonName, + Version: testPythonVersion39, + FileExtension: testPythonExtension, IsDisabled: &falseValue, } result := service.LanguageToSchema(language) assert.NotNil(t, result) assert.Equal(t, int64(1), result.ID) - assert.Equal(t, "python", result.Type) - assert.Equal(t, "3.9", result.Version) - assert.Equal(t, ".py", result.FileExtension) + assert.Equal(t, testPythonName, result.Type) + assert.Equal(t, testPythonVersion39, result.Version) + assert.Equal(t, testPythonExtension, result.FileExtension) }) } diff --git a/package/service/submission_service_test.go b/package/service/submission_service_test.go index fb81aee6..495ad807 100644 --- a/package/service/submission_service_test.go +++ b/package/service/submission_service_test.go @@ -21,6 +21,13 @@ import ( "gorm.io/gorm" ) +const ( + testRoleAdmin = "admin" + testRoleTeacher = "teacher" + testRoleStudent = "student" + testSortBySubmittedAtDesc = "submitted_at:desc" +) + // testSetup holds all mocks and the service for testing type testSetup struct { ctrl *gomock.Controller @@ -234,7 +241,7 @@ func TestGetAvailableLanguages(t *testing.T) { t.Run("Success", func(t *testing.T) { expectedLanguages := []schemas.LanguageConfig{ {Type: "Python", Version: "3.8"}, - {Type: "Python", Version: "3.9"}, + {Type: "Python", Version: testPythonVersion39}, {Type: "Go", Version: "1.22"}, {Type: "Go", Version: "1.23"}, } @@ -269,10 +276,10 @@ func TestGetAll(t *testing.T) { }{ { name: "Admin retrieves all submissions", - user: &schemas.User{Role: "admin"}, + user: &schemas.User{Role: testRoleAdmin}, userID: nil, expectedMethod: func() *gomock.Call { - return setup.submissionRepository.EXPECT().GetAll(gomock.Any(), 10, 0, "submitted_at:desc").Return([]models.Submission{ + return setup.submissionRepository.EXPECT().GetAll(gomock.Any(), 10, 0, testSortBySubmittedAtDesc).Return([]models.Submission{ {ID: 1, TaskID: 1, UserID: 1, Status: types.SubmissionStatusReceived}, {ID: 2, TaskID: 2, UserID: 2, Status: types.SubmissionStatusEvaluated}, }, int64(2), nil).Times(1) @@ -293,10 +300,10 @@ func TestGetAll(t *testing.T) { }, { name: "Teacher retrieves submissions for their tasks", - user: &schemas.User{Role: "teacher", ID: 1}, + user: &schemas.User{Role: testRoleTeacher, ID: 1}, userID: nil, expectedMethod: func() *gomock.Call { - return setup.submissionRepository.EXPECT().GetAllForTeacher(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + return setup.submissionRepository.EXPECT().GetAllForTeacher(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( []models.Submission{ {ID: 1, TaskID: 1, UserID: 1, Status: types.SubmissionStatusReceived}, {ID: 2, TaskID: 2, UserID: 2, Status: types.SubmissionStatusEvaluated}, @@ -318,10 +325,10 @@ func TestGetAll(t *testing.T) { }, { name: "Student retrieves their own submissions", - user: &schemas.User{Role: "student", ID: 1}, + user: &schemas.User{Role: testRoleStudent, ID: 1}, userID: nil, expectedMethod: func() *gomock.Call { - return setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + return setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( []models.Submission{ {ID: 1, TaskID: 1, UserID: 1, Status: types.SubmissionStatusReceived}, }, int64(1), nil).Times(1) @@ -341,10 +348,10 @@ func TestGetAll(t *testing.T) { }, { name: "Error retrieving submissions", - user: &schemas.User{Role: "admin"}, + user: &schemas.User{Role: testRoleAdmin}, userID: nil, expectedMethod: func() *gomock.Call { - return setup.submissionRepository.EXPECT().GetAll(gomock.Any(), 10, 0, "submitted_at:desc").Return( + return setup.submissionRepository.EXPECT().GetAll(gomock.Any(), 10, 0, testSortBySubmittedAtDesc).Return( nil, int64(0), gorm.ErrInvalidData, ).Times(1) }, @@ -375,13 +382,13 @@ func TestGetAllSignedURLs(t *testing.T) { setup := setupSubmissionServiceTest(t) defer setup.ctrl.Finish() - setup.submissionRepository.EXPECT().GetAll(gomock.Any(), 10, 0, "submitted_at:desc").Return([]models.Submission{ + setup.submissionRepository.EXPECT().GetAll(gomock.Any(), 10, 0, testSortBySubmittedAtDesc).Return([]models.Submission{ {ID: 1, TaskID: 1, UserID: 1, Status: types.SubmissionStatusReceived, File: models.File{Path: "tasks/1/submissions/1/solution.py"}}, {ID: 2, TaskID: 2, UserID: 2, Status: types.SubmissionStatusEvaluated}, }, int64(2), nil).Times(1) paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: ""} - result, err := setup.service.GetAll(nil, &schemas.User{Role: "admin"}, nil, nil, nil, paginationParams) + result, err := setup.service.GetAll(nil, &schemas.User{Role: testRoleAdmin}, nil, nil, nil, paginationParams) require.NoError(t, err) require.NotNil(t, result) @@ -398,9 +405,9 @@ func TestGetSignedTestResultURLs(t *testing.T) { setup := setupSubmissionServiceTest(t) defer setup.ctrl.Finish() - stdout := models.File{Path: "solution/1/1/1/stdout/1.out", Bucket: "maxit"} - stderr := models.File{Path: "solution/1/1/1/stderr/1.err", Bucket: "maxit"} - diff := models.File{Path: "solution/1/1/1/diff/1.diff", Bucket: "maxit"} + stdout := models.File{Path: "solution/1/1/1/stdout/1.out", Bucket: testBucket} + stderr := models.File{Path: "solution/1/1/1/stderr/1.err", Bucket: testBucket} + diff := models.File{Path: "solution/1/1/1/diff/1.diff", Bucket: testBucket} submission := &models.Submission{ ID: 1, @@ -424,7 +431,7 @@ func TestGetSignedTestResultURLs(t *testing.T) { setup.submissionRepository.EXPECT().Get(gomock.Any(), int64(1)).Return(submission, nil).Times(1) - result, err := setup.service.Get(nil, 1, &schemas.User{Role: "admin"}) + result, err := setup.service.Get(nil, 1, &schemas.User{Role: testRoleAdmin}) require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Result) @@ -454,19 +461,19 @@ func TestGet(t *testing.T) { }{ { name: "Admin retrieves a submission", - user: &schemas.User{Role: "admin"}, + user: &schemas.User{Role: testRoleAdmin}, expectedSubmission: &models.Submission{ID: 1, TaskID: 1, UserID: 1, Status: types.SubmissionStatusReceived}, expectedErr: false, }, { name: "Student tries to access another user's submission", - user: &schemas.User{Role: "student", ID: 1}, + user: &schemas.User{Role: testRoleStudent, ID: 1}, expectedSubmission: &models.Submission{ID: 1, TaskID: 1, UserID: 2, Status: types.SubmissionStatusReceived}, expectedErr: true, }, { name: "Teacher tries to access a submission for a task they didn't create", - user: &schemas.User{Role: "teacher", ID: 2}, + user: &schemas.User{Role: testRoleTeacher, ID: 2}, expectedSubmission: &models.Submission{ ID: 1, TaskID: 1, @@ -477,7 +484,7 @@ func TestGet(t *testing.T) { }, { name: "Teacher retrieves a submission for a task they created", - user: &schemas.User{Role: "teacher", ID: 2}, + user: &schemas.User{Role: testRoleTeacher, ID: 2}, expectedSubmission: &models.Submission{ ID: 1, TaskID: 1, @@ -488,13 +495,13 @@ func TestGet(t *testing.T) { }, { name: "Error retrieving submission", - user: &schemas.User{Role: "admin"}, + user: &schemas.User{Role: testRoleAdmin}, expectedSubmission: nil, expectedErr: true, }, { name: "Teacher has contest manage permission", - user: &schemas.User{Role: "teacher", ID: 2}, + user: &schemas.User{Role: testRoleTeacher, ID: 2}, expectedSubmission: &models.Submission{ ID: 1, TaskID: 1, @@ -515,7 +522,7 @@ func TestGet(t *testing.T) { } // Expect access control checks for teacher role - if tc.user.Role == "teacher" && tc.expectedSubmission != nil { + if tc.user.Role == testRoleTeacher && tc.expectedSubmission != nil { // If this is the contest path (task check fails, contest check passes) if tc.expectedSubmission.ContestID != nil && !tc.expectedErr && tc.name == "Teacher has contest manage permission" { setup.accessControlService.EXPECT(). @@ -564,7 +571,7 @@ func TestGet(t *testing.T) { Return(expectedSubmission, nil). Times(1) - user := &schemas.User{Role: "admin"} + user := &schemas.User{Role: testRoleAdmin} result, err := setup.service.Get(nil, 1, user) require.NoError(t, err) @@ -586,12 +593,12 @@ func TestSubmissionGetAllForUser(t *testing.T) { {ID: 2, TaskID: 2, UserID: 1, Status: types.SubmissionStatusEvaluated}, } - setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( expectedSubmissions, int64(len(expectedSubmissions)), nil, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "admin"} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleAdmin} submissions, err := setup.service.GetAllForUser(nil, 1, user, paginationParams) @@ -604,12 +611,12 @@ func TestSubmissionGetAllForUser(t *testing.T) { {ID: 1, TaskID: 1, UserID: 1, Status: types.SubmissionStatusReceived}, } - setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( expectedSubmissions, int64(len(expectedSubmissions)), nil, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "student", ID: 1} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleStudent, ID: 1} submissions, err := setup.service.GetAllForUser(nil, 1, user, paginationParams) @@ -618,14 +625,14 @@ func TestSubmissionGetAllForUser(t *testing.T) { }) t.Run("Student tries to retrieve another user's submissions", func(t *testing.T) { - user := &schemas.User{Role: "student", ID: 2} + user := &schemas.User{Role: testRoleStudent, ID: 2} expectedSubmissions := []models.Submission{ {ID: 1, TaskID: 1, UserID: 1, Status: types.SubmissionStatusReceived}, } - setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( expectedSubmissions, int64(len(expectedSubmissions)), nil, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} submissions, err := setup.service.GetAllForUser(nil, 1, user, paginationParams) @@ -639,12 +646,12 @@ func TestSubmissionGetAllForUser(t *testing.T) { {ID: 2, TaskID: 2, UserID: 1, Status: types.SubmissionStatusEvaluated, Task: models.Task{CreatedBy: 2}}, } - setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( expectedSubmissions, int64(len(expectedSubmissions)), nil, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "teacher", ID: 2} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleTeacher, ID: 2} submissions, err := setup.service.GetAllForUser(nil, 1, user, paginationParams) @@ -657,12 +664,12 @@ func TestSubmissionGetAllForUser(t *testing.T) { {ID: 1, TaskID: 1, UserID: 1, Status: types.SubmissionStatusReceived, Task: models.Task{CreatedBy: 3}}, } - setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( expectedSubmissions, int64(len(expectedSubmissions)), nil, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "teacher", ID: 2} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleTeacher, ID: 2} submissions, err := setup.service.GetAllForUser(nil, 1, user, paginationParams) @@ -671,12 +678,12 @@ func TestSubmissionGetAllForUser(t *testing.T) { }) t.Run("Error retrieving submissions", func(t *testing.T) { - setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllByUser(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( nil, int64(0), gorm.ErrInvalidData, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "admin"} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleAdmin} submissions, err := setup.service.GetAllForUser(nil, 1, user, paginationParams) @@ -695,12 +702,12 @@ func TestGetAllForTask(t *testing.T) { {ID: 2, TaskID: 1, UserID: 2, Status: types.SubmissionStatusEvaluated}, } - setup.submissionRepository.EXPECT().GetAllForTask(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllForTask(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( expectedSubmissions, int64(len(expectedSubmissions)), nil, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "admin"} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleAdmin} submissions, err := setup.service.GetAllForTask(nil, 1, user, paginationParams) @@ -716,12 +723,12 @@ func TestGetAllForTask(t *testing.T) { } setup.taskService.EXPECT().Get(gomock.Any(), gomock.Any(), int64(1)).Return(expectedTask, nil).Times(1) - setup.submissionRepository.EXPECT().GetAllForTask(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllForTask(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( expectedSubmissions, int64(len(expectedSubmissions)), nil, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "teacher", ID: 2} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleTeacher, ID: 2} submissions, err := setup.service.GetAllForTask(nil, 1, user, paginationParams) @@ -732,8 +739,8 @@ func TestGetAllForTask(t *testing.T) { t.Run("Teacher tries to retrieve submissions for a task they didn't create", func(t *testing.T) { expectedTask := &schemas.TaskDetailed{ID: 1, CreatedBy: 2} setup.taskService.EXPECT().Get(gomock.Any(), gomock.Any(), int64(1)).Return(expectedTask, nil).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "teacher", ID: 3} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleTeacher, ID: 3} submissions, err := setup.service.GetAllForTask(nil, 1, user, paginationParams) @@ -742,8 +749,8 @@ func TestGetAllForTask(t *testing.T) { }) t.Run("Teacher tries to retrieve submissions for a task, but task get fails", func(t *testing.T) { setup.taskService.EXPECT().Get(gomock.Any(), gomock.Any(), int64(1)).Return(nil, gorm.ErrRecordNotFound).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "teacher", ID: 3} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleTeacher, ID: 3} submissions, err := setup.service.GetAllForTask(nil, 1, user, paginationParams) @@ -757,12 +764,12 @@ func TestGetAllForTask(t *testing.T) { } setup.userService.EXPECT().IsTaskAssignedToUser(gomock.Any(), int64(1), int64(1)).Return(true, nil).Times(1) - setup.submissionRepository.EXPECT().GetAllForTaskByUser(gomock.Any(), int64(1), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllForTaskByUser(gomock.Any(), int64(1), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( expectedSubmissions, int64(len(expectedSubmissions)), nil, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "student", ID: 1} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleStudent, ID: 1} submissions, err := setup.service.GetAllForTask(nil, 1, user, paginationParams) @@ -773,8 +780,8 @@ func TestGetAllForTask(t *testing.T) { t.Run("Student retrieves submissions for a task, but can't check if he is assigned", func(t *testing.T) { setup.userService.EXPECT().IsTaskAssignedToUser(gomock.Any(), int64(1), int64(1)).Return(false, gorm.ErrRecordNotFound).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "student", ID: 1} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleStudent, ID: 1} submissions, err := setup.service.GetAllForTask(nil, 1, user, paginationParams) @@ -784,8 +791,8 @@ func TestGetAllForTask(t *testing.T) { t.Run("Student tries to retrieve submissions for a task they are not assigned to", func(t *testing.T) { setup.userService.EXPECT().IsTaskAssignedToUser(gomock.Any(), int64(1), int64(1)).Return(false, nil).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "student", ID: 1} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleStudent, ID: 1} submissions, err := setup.service.GetAllForTask(nil, 1, user, paginationParams) @@ -794,12 +801,12 @@ func TestGetAllForTask(t *testing.T) { }) t.Run("Error retrieving submissions for a task", func(t *testing.T) { - setup.submissionRepository.EXPECT().GetAllForTask(gomock.Any(), int64(1), 10, 0, "submitted_at:desc").Return( + setup.submissionRepository.EXPECT().GetAllForTask(gomock.Any(), int64(1), 10, 0, testSortBySubmittedAtDesc).Return( nil, int64(0), gorm.ErrInvalidData, ).Times(1) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "submitted_at:desc"} - user := &schemas.User{Role: "admin"} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: testSortBySubmittedAtDesc} + user := &schemas.User{Role: testRoleAdmin} submissions, err := setup.service.GetAllForTask(nil, 1, user, paginationParams) diff --git a/package/service/task_service_test.go b/package/service/task_service_test.go index b053651b..de7f21ee 100644 --- a/package/service/task_service_test.go +++ b/package/service/task_service_test.go @@ -29,6 +29,20 @@ var ( adminUser = &schemas.User{ID: 1, Role: types.UserRoleAdmin} ) +const ( + testTaskTitle = "Test Task" + sortIDAsc = "id:asc" + testTaskOneTitle = "Task 1" + testDescriptionFilename = "description.pdf" + testDescriptionFilePath = "task/1/description.pdf" + testInputFilename = "1.in" + testInputFilePath = "task/1/input/1.in" + testOutputFilename = "1.out" + testOutputFilePath = "task/1/output/1.out" + testBucket = "maxit" + testFilestorageServerType = "filestorage" +) + func addDescription(t *testing.T, zipWriter *zip.Writer) { // Create description.pdf descriptionFile, err := zipWriter.Create("folder/description.pdf") @@ -76,7 +90,7 @@ func createTestArchive(t *testing.T, caseType string) string { addInputOutputFiles(t, zipWriter, 4, "folder", "folder") case "single_file": // Create only one input and output file - _, err := zipWriter.Create("1.in") + _, err := zipWriter.Create(testInputFilename) require.NoError(t, err) case "nonexistent_file": // Create an invalid archive @@ -131,7 +145,7 @@ func TestCreateTask(t *testing.T) { ts := service.NewTaskService(nil, fr, tr, io, ur, gr, nil, nil, acs) t.Run("Success", func(t *testing.T) { task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } ur.EXPECT().Get(gomock.Any(), gomock.Any()).Return(&models.User{ID: 1, Role: types.UserRoleAdmin}, nil).Times(1) @@ -168,7 +182,7 @@ func TestCreateTask(t *testing.T) { t.Run("Non unique title", func(t *testing.T) { task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } tr.EXPECT().GetByTitle(db, task.Title).Return(&models.Task{ @@ -204,7 +218,7 @@ func TestGetTaskByTitle(t *testing.T) { t.Run("Success", func(t *testing.T) { taskID := int64(1) task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } tr.EXPECT().GetByTitle(db, task.Title).Return(&models.Task{ @@ -237,7 +251,7 @@ func TestGetAllTasks(t *testing.T) { fr := mock_repository.NewMockFile(ctrl) ts := service.NewTaskService(nil, fr, tr, io, ur, gr, nil, nil, nil) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "id:asc"} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: sortIDAsc} t.Run("No tasks", func(t *testing.T) { tr.EXPECT().GetAll(db, @@ -254,7 +268,7 @@ func TestGetAllTasks(t *testing.T) { tasks := []models.Task{ { ID: 1, - Title: "Test Task", + Title: testTaskTitle, CreatedBy: teacherUser.ID, IsVisible: true, }, @@ -302,7 +316,7 @@ func TestGetTask(t *testing.T) { ts := service.NewTaskService(fsMock, fr, tr, io, ur, gr, nil, nil, nil) task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } @@ -426,7 +440,7 @@ func TestEditTask(t *testing.T) { t.Run("Success", func(t *testing.T) { task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } tr.EXPECT().Get(db, taskID).Return(&models.Task{ @@ -478,7 +492,7 @@ func TestGetAllCreatedTasks(t *testing.T) { fr := mock_repository.NewMockFile(ctrl) ts := service.NewTaskService(nil, fr, tr, io, ur, gr, nil, nil, nil) taskID := int64(1) - queryParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "id:asc"} + queryParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: sortIDAsc} t.Run("No tasks", func(t *testing.T) { tr.EXPECT().GetAllCreated( @@ -496,7 +510,7 @@ func TestGetAllCreatedTasks(t *testing.T) { t.Run("Success with admin", func(t *testing.T) { task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } tr.EXPECT().GetAllCreated( @@ -782,12 +796,12 @@ func TestGetMyLiveTasks(t *testing.T) { taskID := int64(1) contestTasksMap := map[int64][]models.Task{ contestID: { - {ID: taskID, Title: "Task 1", CreatedBy: teacherUser.ID}, + {ID: taskID, Title: testTaskOneTitle, CreatedBy: teacherUser.ID}, }, } contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, } tr.EXPECT().GetLiveAssignedTasksGroupedByContest(db, studentUser.ID, paginationParams.Limit, paginationParams.Offset).Return(contestTasksMap, nil).Times(1) @@ -800,7 +814,7 @@ func TestGetMyLiveTasks(t *testing.T) { assert.NotNil(t, result) assert.Len(t, result.Contests, 1) assert.Equal(t, contestID, result.Contests[0].ContestID) - assert.Equal(t, "Test Contest", result.Contests[0].ContestName) + assert.Equal(t, testContestName, result.Contests[0].ContestName) assert.Len(t, result.Contests[0].Tasks, 1) assert.Equal(t, taskID, result.Contests[0].Tasks[0].ID) assert.Equal(t, 2, result.Contests[0].Tasks[0].AttemptsSummary.AttemptCount) @@ -812,12 +826,12 @@ func TestGetMyLiveTasks(t *testing.T) { taskID := int64(1) contestTasksMap := map[int64][]models.Task{ contestID: { - {ID: taskID, Title: "Task 1", CreatedBy: teacherUser.ID}, + {ID: taskID, Title: testTaskOneTitle, CreatedBy: teacherUser.ID}, }, } contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, } tr.EXPECT().GetLiveAssignedTasksGroupedByContest(db, studentUser.ID, paginationParams.Limit, paginationParams.Offset).Return(contestTasksMap, nil).Times(1) @@ -846,7 +860,7 @@ func TestGetMyLiveTasks(t *testing.T) { taskID := int64(1) contestTasksMap := map[int64][]models.Task{ contestID: { - {ID: taskID, Title: "Task 1", CreatedBy: teacherUser.ID}, + {ID: taskID, Title: testTaskOneTitle, CreatedBy: teacherUser.ID}, }, } @@ -865,12 +879,12 @@ func TestGetMyLiveTasks(t *testing.T) { taskID := int64(1) contestTasksMap := map[int64][]models.Task{ contestID: { - {ID: taskID, Title: "Task 1", CreatedBy: teacherUser.ID}, + {ID: taskID, Title: testTaskOneTitle, CreatedBy: teacherUser.ID}, }, } contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, } tr.EXPECT().GetLiveAssignedTasksGroupedByContest(db, studentUser.ID, paginationParams.Limit, paginationParams.Offset).Return(contestTasksMap, nil).Times(1) @@ -890,12 +904,12 @@ func TestGetMyLiveTasks(t *testing.T) { taskID := int64(1) contestTasksMap := map[int64][]models.Task{ contestID: { - {ID: taskID, Title: "Task 1", CreatedBy: teacherUser.ID}, + {ID: taskID, Title: testTaskOneTitle, CreatedBy: teacherUser.ID}, }, } contest := &models.Contest{ ID: contestID, - Name: "Test Contest", + Name: testContestName, } tr.EXPECT().GetLiveAssignedTasksGroupedByContest(db, studentUser.ID, paginationParams.Limit, paginationParams.Offset).Return(contestTasksMap, nil).Times(1) @@ -931,23 +945,23 @@ func TestProcessAndUpload(t *testing.T) { archivePath := "/test/archive.zip" task := &models.Task{ ID: taskID, - Title: "Test Task", + Title: testTaskTitle, CreatedBy: teacherUser.ID, } t.Run("Success", func(t *testing.T) { uploadedFiles := &filestorage.UploadedTaskFiles{ DescriptionFile: filestorage.UploadedFile{ - Filename: "description.pdf", - Path: "task/1/description.pdf", - Bucket: "maxit", - ServerType: "filestorage", + Filename: testDescriptionFilename, + Path: testDescriptionFilePath, + Bucket: testBucket, + ServerType: testFilestorageServerType, }, InputFiles: []filestorage.UploadedFile{ - {Filename: "1.in", Path: "task/1/input/1.in", Bucket: "maxit", ServerType: "filestorage"}, + {Filename: testInputFilename, Path: testInputFilePath, Bucket: testBucket, ServerType: testFilestorageServerType}, }, OutputFiles: []filestorage.UploadedFile{ - {Filename: "1.out", Path: "task/1/output/1.out", Bucket: "maxit", ServerType: "filestorage"}, + {Filename: testOutputFilename, Path: testOutputFilePath, Bucket: testBucket, ServerType: testFilestorageServerType}, }, } @@ -1002,8 +1016,8 @@ func TestProcessAndUpload(t *testing.T) { t.Run("Save description file error", func(t *testing.T) { uploadedFiles := &filestorage.UploadedTaskFiles{ DescriptionFile: filestorage.UploadedFile{ - Filename: "description.pdf", - Path: "task/1/description.pdf", + Filename: testDescriptionFilename, + Path: testDescriptionFilePath, }, InputFiles: []filestorage.UploadedFile{}, OutputFiles: []filestorage.UploadedFile{}, @@ -1023,8 +1037,8 @@ func TestProcessAndUpload(t *testing.T) { t.Run("Update task error", func(t *testing.T) { uploadedFiles := &filestorage.UploadedTaskFiles{ DescriptionFile: filestorage.UploadedFile{ - Filename: "description.pdf", - Path: "task/1/description.pdf", + Filename: testDescriptionFilename, + Path: testDescriptionFilePath, }, InputFiles: []filestorage.UploadedFile{}, OutputFiles: []filestorage.UploadedFile{}, @@ -1045,14 +1059,14 @@ func TestProcessAndUpload(t *testing.T) { t.Run("Save input file error", func(t *testing.T) { uploadedFiles := &filestorage.UploadedTaskFiles{ DescriptionFile: filestorage.UploadedFile{ - Filename: "description.pdf", - Path: "task/1/description.pdf", + Filename: testDescriptionFilename, + Path: testDescriptionFilePath, }, InputFiles: []filestorage.UploadedFile{ - {Filename: "1.in", Path: "task/1/input/1.in"}, + {Filename: testInputFilename, Path: testInputFilePath}, }, OutputFiles: []filestorage.UploadedFile{ - {Filename: "1.out", Path: "task/1/output/1.out"}, + {Filename: testOutputFilename, Path: testOutputFilePath}, }, } @@ -1072,14 +1086,14 @@ func TestProcessAndUpload(t *testing.T) { t.Run("Save output file error", func(t *testing.T) { uploadedFiles := &filestorage.UploadedTaskFiles{ DescriptionFile: filestorage.UploadedFile{ - Filename: "description.pdf", - Path: "task/1/description.pdf", + Filename: testDescriptionFilename, + Path: testDescriptionFilePath, }, InputFiles: []filestorage.UploadedFile{ - {Filename: "1.in", Path: "task/1/input/1.in"}, + {Filename: testInputFilename, Path: testInputFilePath}, }, OutputFiles: []filestorage.UploadedFile{ - {Filename: "1.out", Path: "task/1/output/1.out"}, + {Filename: testOutputFilename, Path: testOutputFilePath}, }, } @@ -1099,14 +1113,14 @@ func TestProcessAndUpload(t *testing.T) { t.Run("Create test case error", func(t *testing.T) { uploadedFiles := &filestorage.UploadedTaskFiles{ DescriptionFile: filestorage.UploadedFile{ - Filename: "description.pdf", - Path: "task/1/description.pdf", + Filename: testDescriptionFilename, + Path: testDescriptionFilePath, }, InputFiles: []filestorage.UploadedFile{ - {Filename: "1.in", Path: "task/1/input/1.in"}, + {Filename: testInputFilename, Path: testInputFilePath}, }, OutputFiles: []filestorage.UploadedFile{ - {Filename: "1.out", Path: "task/1/output/1.out"}, + {Filename: testOutputFilename, Path: testOutputFilePath}, }, } @@ -1126,16 +1140,16 @@ func TestProcessAndUpload(t *testing.T) { t.Run("Success with multiple input output files", func(t *testing.T) { uploadedFiles := &filestorage.UploadedTaskFiles{ DescriptionFile: filestorage.UploadedFile{ - Filename: "description.pdf", - Path: "task/1/description.pdf", + Filename: testDescriptionFilename, + Path: testDescriptionFilePath, }, InputFiles: []filestorage.UploadedFile{ - {Filename: "1.in", Path: "task/1/input/1.in"}, + {Filename: testInputFilename, Path: testInputFilePath}, {Filename: "2.in", Path: "task/1/input/2.in"}, {Filename: "3.in", Path: "task/1/input/3.in"}, }, OutputFiles: []filestorage.UploadedFile{ - {Filename: "1.out", Path: "task/1/output/1.out"}, + {Filename: testOutputFilename, Path: testOutputFilePath}, {Filename: "2.out", Path: "task/1/output/2.out"}, {Filename: "3.out", Path: "task/1/output/3.out"}, }, @@ -1291,7 +1305,7 @@ func TestCreateTaskErrors(t *testing.T) { t.Run("Error getting task by title", func(t *testing.T) { task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } tr.EXPECT().GetByTitle(db, task.Title).Return(nil, gorm.ErrInvalidDB).Times(1) @@ -1303,7 +1317,7 @@ func TestCreateTaskErrors(t *testing.T) { t.Run("Error getting user", func(t *testing.T) { task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } tr.EXPECT().GetByTitle(db, task.Title).Return(nil, gorm.ErrRecordNotFound).Times(1) @@ -1316,7 +1330,7 @@ func TestCreateTaskErrors(t *testing.T) { t.Run("Error creating task", func(t *testing.T) { task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } tr.EXPECT().GetByTitle(db, task.Title).Return(nil, gorm.ErrRecordNotFound).Times(1) @@ -1330,7 +1344,7 @@ func TestCreateTaskErrors(t *testing.T) { t.Run("Error granting owner access", func(t *testing.T) { task := &schemas.Task{ - Title: "Test Task", + Title: testTaskTitle, CreatedBy: adminUser.ID, } tr.EXPECT().GetByTitle(db, task.Title).Return(nil, gorm.ErrRecordNotFound).Times(1) diff --git a/package/service/user_service_test.go b/package/service/user_service_test.go index f2778431..2ebfc114 100644 --- a/package/service/user_service_test.go +++ b/package/service/user_service_test.go @@ -19,6 +19,14 @@ import ( "gorm.io/gorm" ) +const ( + testUserName = "Test User" + testSurname = "Test Surname" + testEmail = "email@email.com" + testUsername = "testuser" + testPasswordHash = "password" +) + func TestGetUserByEmail(t *testing.T) { ctrl := gomock.NewController(t) db := &testutils.MockDatabase{} @@ -36,11 +44,11 @@ func TestGetUserByEmail(t *testing.T) { t.Run("User exists", func(t *testing.T) { user := &models.User{ ID: int64(1), - Name: "Test User", - Surname: "Test Surname", - Email: "email@email.com", - Username: "testuser", - PasswordHash: "password", + Name: testUserName, + Surname: testSurname, + Email: testEmail, + Username: testUsername, + PasswordHash: testPasswordHash, } ur.EXPECT().GetByEmail(db, user.Email).Return(user, nil).Times(1) userResp, err := us.GetByEmail(db, user.Email) @@ -71,11 +79,11 @@ func TestGetUserByID(t *testing.T) { t.Run("User exists", func(t *testing.T) { user := &models.User{ ID: int64(1), - Name: "Test User", - Surname: "Test Surname", - Email: "email@email.com", - Username: "testuser", - PasswordHash: "password", + Name: testUserName, + Surname: testSurname, + Email: testEmail, + Username: testUsername, + PasswordHash: testPasswordHash, } ur.EXPECT().Get(db, user.ID).Return(user, nil).Times(1) userResp, err := us.Get(db, user.ID) @@ -133,11 +141,11 @@ func TestEditUser(t *testing.T) { t.Run("Success", func(t *testing.T) { user := &models.User{ ID: 3, - Name: "Test User", - Surname: "Test Surname", - Email: "email@email.com", - Username: "testuser", - PasswordHash: "password", + Name: testUserName, + Surname: testSurname, + Email: testEmail, + Username: testUsername, + PasswordHash: testPasswordHash, } newName := "New Name" updatedUser := &schemas.UserEdit{ @@ -156,7 +164,7 @@ func TestGetAllUsers(t *testing.T) { ur := mock_repository.NewMockUserRepository(ctrl) cs := mock_service.NewMockContestService(ctrl) us := service.NewUserService(ur, cs) - paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: "id:asc"} + paginationParams := schemas.PaginationParams{Limit: 10, Offset: 0, Sort: sortIDAsc} t.Run("No users", func(t *testing.T) { ur.EXPECT().GetAll( @@ -173,11 +181,11 @@ func TestGetAllUsers(t *testing.T) { t.Run("Users exist", func(t *testing.T) { user := &models.User{ - Name: "Test User", - Surname: "Test Surname", - Email: "email@email.com", - Username: "testuser", - PasswordHash: "password", + Name: testUserName, + Surname: testSurname, + Email: testEmail, + Username: testUsername, + PasswordHash: testPasswordHash, } ur.EXPECT().GetAll( db, @@ -247,7 +255,7 @@ func TestChangePassword(t *testing.T) { cs := mock_service.NewMockContestService(ctrl) us := service.NewUserService(ur, cs) - password := "password" + password := testPasswordHash hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) require.NoError(t, err) user := &models.User{