ci/cd: migrate backend linter to v2. fixed unit test workflow (#400)

This commit is contained in:
Rich
2025-03-28 02:00:55 -07:00
committed by GitHub
parent cbd1bbdf74
commit b935a4824a
16 changed files with 95 additions and 53 deletions

View File

@@ -32,8 +32,8 @@ jobs:
go-version-file: backend/go.mod go-version-file: backend/go.mod
- name: Run Golangci-lint - name: Run Golangci-lint
uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 uses: golangci/golangci-lint-action@dec74fa03096ff515422f71d18d41307cacde373 # v7.0.0
with: with:
version: v1.64 version: v2.0.2
working-directory: backend working-directory: backend
only-new-issues: ${{ github.event_name == 'pull_request' }} only-new-issues: ${{ github.event_name == 'pull_request' }}

View File

@@ -2,11 +2,11 @@ name: Unit Tests
on: on:
push: push:
branches: [main] branches: [main]
paths: paths:
- "backend/**" - "backend/**"
pull_request: pull_request:
branches: [main] branches: [main]
paths: paths:
- "backend/**" - "backend/**"
jobs: jobs:
@@ -25,6 +25,7 @@ jobs:
- name: Run backend unit tests - name: Run backend unit tests
working-directory: backend working-directory: backend
run: | run: |
set -e -o pipefail
go test -v ./... | tee /tmp/TestResults.log go test -v ./... | tee /tmp/TestResults.log
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: always()

View File

@@ -41,4 +41,4 @@ EXPOSE 80
ENV APP_ENV=production ENV APP_ENV=production
ENTRYPOINT ["sh", "./scripts/docker/create-user.sh"] ENTRYPOINT ["sh", "./scripts/docker/create-user.sh"]
CMD ["sh", "./scripts/docker/entrypoint.sh"] CMD ["sh", "./scripts/docker/entrypoint.sh"]

View File

@@ -1,25 +1,64 @@
version: "2"
run:
tests: true
timeout: 5m
linters: linters:
# Disable all linters. default: none
# Default: false
disable-all: true
enable: enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- contextcheck
- copyloopvar
- durationcheck
- errcheck - errcheck
- gosimple - errchkjson
- errorlint
- exhaustive
- gocheckcompilerdirectives
- gochecksumtype
- gocognit
- gocritic
- gosec
- gosmopolitan
- govet - govet
- ineffassign - ineffassign
- loggercheck
- makezero
- musttag
- nilerr
- nilnesserr
- noctx
- protogetter
- reassign
- recvcheck
- rowserrcheck
- spancheck
- sqlclosecheck
- staticcheck - staticcheck
- testifylint
- unused - unused
- gosec - usestdlibvars
- gocognit - zerologlint
presets:
- bugs
- sql
exclusions: exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths: paths:
- third_party$
- builtin$
- examples$
- internal/service/test_service.go - internal/service/test_service.go
formatters:
run: enable:
timeout: "5m" - goimports
tests: true exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View File

@@ -1,6 +1,6 @@
module github.com/pocket-id/pocket-id/backend module github.com/pocket-id/pocket-id/backend
go 1.23.1 go 1.23.7
require ( require (
github.com/caarlos0/env/v11 v11.3.1 github.com/caarlos0/env/v11 v11.3.1

View File

@@ -240,12 +240,13 @@ func (oc *OidcController) EndSessionHandler(c *gin.Context) {
var input dto.OidcLogoutDto var input dto.OidcLogoutDto
// Bind query parameters to the struct // Bind query parameters to the struct
if c.Request.Method == http.MethodGet { switch c.Request.Method {
case http.MethodGet:
if err := c.ShouldBindQuery(&input); err != nil { if err := c.ShouldBindQuery(&input); err != nil {
_ = c.Error(err) _ = c.Error(err)
return return
} }
} else if c.Request.Method == http.MethodPost { case http.MethodPost:
// Bind form parameters to the struct // Bind form parameters to the struct
if err := c.ShouldBind(&input); err != nil { if err := c.ShouldBind(&input); err != nil {
_ = c.Error(err) _ = c.Error(err)

View File

@@ -63,13 +63,14 @@ func mapStructInternal(sourceVal reflect.Value, destVal reflect.Value) error {
} }
func mapField(sourceField reflect.Value, destField reflect.Value) error { func mapField(sourceField reflect.Value, destField reflect.Value) error {
if sourceField.Type() == destField.Type() { switch {
case sourceField.Type() == destField.Type():
destField.Set(sourceField) destField.Set(sourceField)
} else if sourceField.Kind() == reflect.Slice && destField.Kind() == reflect.Slice { case sourceField.Kind() == reflect.Slice && destField.Kind() == reflect.Slice:
return mapSlice(sourceField, destField) return mapSlice(sourceField, destField)
} else if sourceField.Kind() == reflect.Struct && destField.Kind() == reflect.Struct { case sourceField.Kind() == reflect.Struct && destField.Kind() == reflect.Struct:
return mapStructInternal(sourceField, destField) return mapStructInternal(sourceField, destField)
} else { default:
return mapSpecialTypes(sourceField, destField) return mapSpecialTypes(sourceField, destField)
} }
return nil return nil
@@ -98,8 +99,7 @@ func mapSlice(sourceField reflect.Value, destField reflect.Value) error {
} }
func mapSpecialTypes(sourceField reflect.Value, destField reflect.Value) error { func mapSpecialTypes(sourceField reflect.Value, destField reflect.Value) error {
switch sourceField.Interface().(type) { if _, ok := sourceField.Interface().(datatype.DateTime); ok {
case datatype.DateTime:
if sourceField.Type() == reflect.TypeOf(datatype.DateTime{}) && destField.Type() == reflect.TypeOf(time.Time{}) { if sourceField.Type() == reflect.TypeOf(datatype.DateTime{}) && destField.Type() == reflect.TypeOf(time.Time{}) {
dateValue := sourceField.Interface().(datatype.DateTime) dateValue := sourceField.Interface().(datatype.DateTime)
destField.Set(reflect.ValueOf(dateValue.ToTime())) destField.Set(reflect.ValueOf(dateValue.ToTime()))

View File

@@ -1,10 +1,11 @@
package dto package dto
import ( import (
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"log" "log"
"regexp" "regexp"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
) )
var validateUsername validator.Func = func(fl validator.FieldLevel) bool { var validateUsername validator.Func = func(fl validator.FieldLevel) bool {

View File

@@ -1,6 +1,8 @@
package middleware package middleware
import ( import (
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pocket-id/pocket-id/backend/internal/common" "github.com/pocket-id/pocket-id/backend/internal/common"
) )
@@ -23,7 +25,7 @@ func (m *CorsMiddleware) Add() gin.HandlerFunc {
c.Writer.Header().Set("Access-Control-Allow-Headers", "*") c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
if c.Request.Method == "OPTIONS" { if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(204) c.AbortWithStatus(204)
return return
} }

View File

@@ -1,8 +1,6 @@
package model package model
import ( import datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/model/types"
)
type ApiKey struct { type ApiKey struct {
Base Base

View File

@@ -4,7 +4,7 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pocket-id/pocket-id/backend/internal/model/types" datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"gorm.io/gorm" "gorm.io/gorm"
) )

View File

@@ -2,10 +2,11 @@ package service
import ( import (
"errors" "errors"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"log" "log"
"time" "time"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/common" "github.com/pocket-id/pocket-id/backend/internal/common"
"github.com/pocket-id/pocket-id/backend/internal/dto" "github.com/pocket-id/pocket-id/backend/internal/dto"
"github.com/pocket-id/pocket-id/backend/internal/model" "github.com/pocket-id/pocket-id/backend/internal/model"

View File

@@ -105,9 +105,10 @@ func (s *CustomClaimService) updateCustomClaims(idType idType, value string, cla
Value: claim.Value, Value: claim.Value,
} }
if idType == UserID { switch idType {
case UserID:
customClaim.UserID = &value customClaim.UserID = &value
} else if idType == UserGroupID { case UserGroupID:
customClaim.UserGroupID = &value customClaim.UserGroupID = &value
} }

View File

@@ -319,7 +319,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) {
assert.False(t, isAdmin, "isAdmin should be false") assert.False(t, isAdmin, "isAdmin should be false")
audience, ok := claims.Audience() audience, ok := claims.Audience()
_ = assert.True(t, ok, "Audience not found in token") && _ = assert.True(t, ok, "Audience not found in token") &&
assert.EqualValues(t, []string{"https://test.example.com"}, audience, "Audience should contain the app URL") assert.Equal(t, []string{"https://test.example.com"}, audience, "Audience should contain the app URL")
// Check token expiration time is approximately 1 hour from now // Check token expiration time is approximately 1 hour from now
expectedExp := time.Now().Add(1 * time.Hour) expectedExp := time.Now().Add(1 * time.Hour)
@@ -606,7 +606,7 @@ func TestGenerateVerifyIdToken(t *testing.T) {
assert.Equal(t, "user123", subject, "Token subject should match user ID") assert.Equal(t, "user123", subject, "Token subject should match user ID")
audience, ok := claims.Audience() audience, ok := claims.Audience()
_ = assert.True(t, ok, "Audience not found in token") && _ = assert.True(t, ok, "Audience not found in token") &&
assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID")
issuer, ok := claims.Issuer() issuer, ok := claims.Issuer()
_ = assert.True(t, ok, "Issuer not found in token") && _ = assert.True(t, ok, "Issuer not found in token") &&
assert.Equal(t, common.EnvConfig.AppURL, issuer, "Issuer should match app URL") assert.Equal(t, common.EnvConfig.AppURL, issuer, "Issuer should match app URL")
@@ -935,7 +935,7 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) {
assert.Equal(t, user.ID, subject, "Token subject should match user ID") assert.Equal(t, user.ID, subject, "Token subject should match user ID")
audience, ok := claims.Audience() audience, ok := claims.Audience()
_ = assert.True(t, ok, "Audience not found in token") && _ = assert.True(t, ok, "Audience not found in token") &&
assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID")
issuer, ok := claims.Issuer() issuer, ok := claims.Issuer()
_ = assert.True(t, ok, "Issuer not found in token") && _ = assert.True(t, ok, "Issuer not found in token") &&
assert.Equal(t, common.EnvConfig.AppURL, issuer, "Issuer should match app URL") assert.Equal(t, common.EnvConfig.AppURL, issuer, "Issuer should match app URL")
@@ -1050,7 +1050,7 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) {
assert.Equal(t, user.ID, subject, "Token subject should match user ID") assert.Equal(t, user.ID, subject, "Token subject should match user ID")
audience, ok := claims.Audience() audience, ok := claims.Audience()
_ = assert.True(t, ok, "Audience not found in token") && _ = assert.True(t, ok, "Audience not found in token") &&
assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID")
// Verify the key type is OKP // Verify the key type is OKP
publicKey, err := service.GetPublicJWK() publicKey, err := service.GetPublicJWK()
@@ -1104,7 +1104,7 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) {
assert.Equal(t, user.ID, subject, "Token subject should match user ID") assert.Equal(t, user.ID, subject, "Token subject should match user ID")
audience, ok := claims.Audience() audience, ok := claims.Audience()
_ = assert.True(t, ok, "Audience not found in token") && _ = assert.True(t, ok, "Audience not found in token") &&
assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID")
// Verify the key type is EC // Verify the key type is EC
publicKey, err := service.GetPublicJWK() publicKey, err := service.GetPublicJWK()
@@ -1158,7 +1158,7 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) {
assert.Equal(t, user.ID, subject, "Token subject should match user ID") assert.Equal(t, user.ID, subject, "Token subject should match user ID")
audience, ok := claims.Audience() audience, ok := claims.Audience()
_ = assert.True(t, ok, "Audience not found in token") && _ = assert.True(t, ok, "Audience not found in token") &&
assert.EqualValues(t, []string{clientID}, audience, "Audience should contain the client ID") assert.Equal(t, []string{clientID}, audience, "Audience should contain the client ID")
// Verify the key type is RSA // Verify the key type is RSA
publicKey, err := service.GetPublicJWK() publicKey, err := service.GetPublicJWK()

View File

@@ -53,8 +53,8 @@ func TestGetAuthenticatorName(t *testing.T) {
// Inject a test AAGUID map // Inject a test AAGUID map
aaguidMap = map[string]string{ aaguidMap = map[string]string{
"adce0002-35bc-c60a-648b-m0b25f1f05503": "Test Authenticator", "adce0002-35bc-c60a-648b-0b25f1f05503": "Test Authenticator",
"00000000-0000-0000-0000-000000000000": "Zero Authenticator", "00000000-0000-0000-0000-000000000000": "Zero Authenticator",
} }
aaguidMapOnce = sync.Once{} aaguidMapOnce = sync.Once{}
aaguidMapOnce.Do(func() {}) // Mark as done to avoid loading from file aaguidMapOnce.Do(func() {}) // Mark as done to avoid loading from file

View File

@@ -171,14 +171,12 @@ func (c *Composer) String() string {
func convertRunes(str string) []string { func convertRunes(str string) []string {
var enc = make([]string, 0, len(str)) var enc = make([]string, 0, len(str))
for _, r := range str { for _, r := range str {
if r == ' ' { switch {
case r == ' ':
enc = append(enc, "_") enc = append(enc, "_")
} else if isPrintableASCIIRune(r) && case isPrintableASCIIRune(r) && r != '=' && r != '?' && r != '_':
r != '=' &&
r != '?' &&
r != '_' {
enc = append(enc, string(r)) enc = append(enc, string(r))
} else { default:
enc = append(enc, string(toHex([]byte(string(r))))) enc = append(enc, string(toHex([]byte(string(r)))))
} }
} }