fix: resolve all golangci-lint v2 failures
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

Production fixes:
- cmd/server/main.go: refactor to run() helper to eliminate exitAfterDefer
  (os.Exit in main() no longer bypasses deferred s.Close)
- internal/cast/client.go: use _ = resp.Body.Close() (errcheck)
- internal/ghl/api.go: wrap both defers as func(){ _ = resp.Body.Close() }()

Test fixes:
- internal/cast/client_test.go: replace err.(*CastAPIError) type assertions
  with errors.As (errorlint)

Config:
- .golangci.yml: use explicit path regex .*_test\\.go and add errorlint
  to test-file exclusions

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Head of Product & Engineering 2026-04-05 21:52:36 +02:00
parent 8f2080203d
commit 6a853f6566
5 changed files with 26 additions and 20 deletions

View File

@ -18,7 +18,8 @@ linters:
issues: issues:
exclusions: exclusions:
rules: rules:
- path: _test\.go - path: ".*_test\\.go"
linters: linters:
- errcheck - errcheck
- gocritic - gocritic
- errorlint

View File

@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"fmt"
"log/slog" "log/slog"
"net/http" "net/http"
"os" "os"
@ -20,21 +21,25 @@ import (
func main() { func main() {
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))) slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo})))
if err := run(); err != nil {
slog.Error("fatal error", "err", err)
os.Exit(1)
}
}
func run() error {
cfg, err := config.Load() cfg, err := config.Load()
if err != nil { if err != nil {
slog.Error("config error", "err", err) return fmt.Errorf("config: %w", err)
os.Exit(1)
} }
ctx := context.Background() ctx := context.Background()
s, err := store.NewStore(ctx, cfg.MongoURI) s, err := store.NewStore(ctx, cfg.MongoURI)
if err != nil { if err != nil {
slog.Error("failed to connect to mongodb", "err", err) return fmt.Errorf("mongodb: %w", err)
os.Exit(1)
} }
defer s.Close(ctx) defer func() { _ = s.Close(ctx) }()
castClient := cast.NewClient(cfg.CastAPIURL, cfg.CastAPIKey, cfg.CastSenderID) castClient := cast.NewClient(cfg.CastAPIURL, cfg.CastAPIKey, cfg.CastSenderID)
ghlAPI := ghl.NewAPIClient() ghlAPI := ghl.NewAPIClient()
@ -42,8 +47,7 @@ func main() {
webhookHandler, err := ghl.NewWebhookHandler(cfg.GHLWebhookPublicKey, castClient, ghlAPI, oauthHandler, s) webhookHandler, err := ghl.NewWebhookHandler(cfg.GHLWebhookPublicKey, castClient, ghlAPI, oauthHandler, s)
if err != nil { if err != nil {
slog.Error("failed to initialize webhook handler", "err", err) return fmt.Errorf("webhook handler: %w", err)
os.Exit(1)
} }
r := chi.NewRouter() r := chi.NewRouter()
@ -77,9 +81,9 @@ func main() {
slog.Info("cast-ghl-provider started", "port", cfg.Port, "base_url", cfg.BaseURL) slog.Info("cast-ghl-provider started", "port", cfg.Port, "base_url", cfg.BaseURL)
if err := srv.ListenAndServe(); err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != http.ErrServerClosed {
slog.Error("server error", "err", err) return fmt.Errorf("server: %w", err)
os.Exit(1)
} }
return nil
} }
func healthCheck(w http.ResponseWriter, r *http.Request) { func healthCheck(w http.ResponseWriter, r *http.Request) {

View File

@ -60,7 +60,7 @@ func (c *Client) SendSMS(ctx context.Context, to, message string) (*SendResponse
} }
} }
slog.Warn("cast api rate limited, retrying", "attempt", attempt+1, "wait", wait) slog.Warn("cast api rate limited, retrying", "attempt", attempt+1, "wait", wait)
resp.Body.Close() _ = resp.Body.Close()
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
@ -70,7 +70,7 @@ func (c *Client) SendSMS(ctx context.Context, to, message string) (*SendResponse
} }
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
resp.Body.Close() _ = resp.Body.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,6 +3,7 @@ package cast
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"sync/atomic" "sync/atomic"
@ -52,8 +53,8 @@ func TestSendSMS_APIError(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected error, got nil") t.Fatal("expected error, got nil")
} }
castErr, ok := err.(*CastAPIError) var castErr *CastAPIError
if !ok { if !errors.As(err, &castErr) {
t.Fatalf("expected CastAPIError, got %T", err) t.Fatalf("expected CastAPIError, got %T", err)
} }
if castErr.StatusCode != http.StatusPaymentRequired { if castErr.StatusCode != http.StatusPaymentRequired {
@ -73,8 +74,8 @@ func TestSendSMS_SuccessFalseInBody(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected error, got nil") t.Fatal("expected error, got nil")
} }
castErr, ok := err.(*CastAPIError) var castErr *CastAPIError
if !ok { if !errors.As(err, &castErr) {
t.Fatalf("expected CastAPIError, got %T", err) t.Fatalf("expected CastAPIError, got %T", err)
} }
if castErr.APIError != "invalid number" { if castErr.APIError != "invalid number" {
@ -133,8 +134,8 @@ func TestSendSMS_Unauthorized(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected error for 401, got nil") t.Fatal("expected error for 401, got nil")
} }
castErr, ok := err.(*CastAPIError) var castErr *CastAPIError
if !ok { if !errors.As(err, &castErr) {
t.Fatalf("expected CastAPIError, got %T: %v", err, err) t.Fatalf("expected CastAPIError, got %T: %v", err, err)
} }
if castErr.StatusCode != http.StatusUnauthorized { if castErr.StatusCode != http.StatusUnauthorized {

View File

@ -44,7 +44,7 @@ func (c *APIClient) UpdateMessageStatus(ctx context.Context, accessToken, messag
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return fmt.Errorf("ghl update status: failed to read response body: %w", err) return fmt.Errorf("ghl update status: failed to read response body: %w", err)
@ -75,7 +75,7 @@ func (c *APIClient) PostInboundMessage(ctx context.Context, accessToken string,
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close() defer func() { _ = resp.Body.Close() }()
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err