Skip to content

Commit d8dcf06

Browse files
committed
Introduced gofmt and golangci-lint to GitHub Actions, with a corresponding code cleanup
1 parent fa27dcd commit d8dcf06

File tree

14 files changed

+213
-115
lines changed

14 files changed

+213
-115
lines changed

.github/workflows/build.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ jobs:
3333
- name: Build
3434
run: go build -v ./...
3535

36+
- name: Check formatting (gofmt)
37+
run: |
38+
fmtres=$(gofmt -l .)
39+
if [ -n "$fmtres" ]; then
40+
echo "These files are not gofmt'ed:"
41+
echo "$fmtres"
42+
exit 1
43+
fi
44+
45+
- name: Run golangci-lint
46+
uses: golangci/golangci-lint-action@v8
47+
with:
48+
version: v2.6
49+
3650
- name: Run unit tests
3751
run: go test -v -short ./...
3852

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
# Notes API: Golang with NoSQL Databases
2+
13
[![Author](https://img.shields.io/badge/Author-Vadim%20Starichkov-blue?style=for-the-badge)](https://github.com/starichkov)
24
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/starichkov/golang-simple-notes?style=for-the-badge)
35
[![GitHub License](https://img.shields.io/github/license/starichkov/golang-simple-notes?style=for-the-badge)](https://github.com/starichkov/golang-simple-notes/blob/main/LICENSE.md)
46
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/starichkov/golang-simple-notes/build.yml?style=for-the-badge)](https://github.com/starichkov/golang-simple-notes/actions/workflows/build.yml)
57
[![Codecov](https://img.shields.io/codecov/c/github/starichkov/golang-simple-notes?style=for-the-badge)](https://codecov.io/gh/starichkov/golang-simple-notes)
68

7-
Notes API: Golang with NoSQL Databases
8-
=
9-
109
A simple microservice for notes management with REST and gRPC APIs, with support for multiple storage backends.
1110

1211
*This project is generated using JetBrains Junie and several other AI coding agents to evaluate agent capabilities.*

app_test.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ func TestApp_InitializeWithCouchDB(t *testing.T) {
5353

5454
defer func() {
5555
// Clean up: delete the notes database
56-
req, _ := http.NewRequest(http.MethodDelete, couchURL+"/notes", nil)
57-
http.DefaultClient.Do(req)
56+
req, err := http.NewRequest(http.MethodDelete, couchURL+"/notes", nil)
57+
if err != nil {
58+
return
59+
}
60+
resp, err := http.DefaultClient.Do(req)
61+
if err == nil && resp != nil {
62+
_ = resp.Body.Close()
63+
}
5864
}()
5965

6066
// Create app with CouchDB config
@@ -81,7 +87,7 @@ func TestApp_InitializeWithCouchDB(t *testing.T) {
8187
if err != nil {
8288
t.Fatalf("Failed to create CouchDB database: %v", err)
8389
}
84-
defer resp.Body.Close()
90+
defer func() { _ = resp.Body.Close() }()
8591
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusPreconditionFailed {
8692
t.Fatalf("Failed to create CouchDB database: %s", resp.Status)
8793
}
@@ -108,7 +114,9 @@ func TestApp_InitializeWithCouchDB(t *testing.T) {
108114
}
109115

110116
// Clean up
111-
defer app.storage.Close(ctx)
117+
if err := app.storage.Close(ctx); err != nil {
118+
t.Logf("storage close error: %v", err)
119+
}
112120
}
113121

114122
func TestApp_InitializeWithMongoDB(t *testing.T) {
@@ -171,7 +179,9 @@ func TestApp_InitializeWithMongoDB(t *testing.T) {
171179
}
172180

173181
// Clean up
174-
defer app.storage.Close(ctx)
182+
if err := app.storage.Close(ctx); err != nil {
183+
t.Logf("storage close error: %v", err)
184+
}
175185
}
176186

177187
func TestApp_CreateSampleNotes(t *testing.T) {
@@ -574,7 +584,9 @@ func TestApp_StartServers(t *testing.T) {
574584
// Clean up
575585
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
576586
defer cancel()
577-
app.restServer.Shutdown(shutdownCtx)
587+
if err := app.restServer.Shutdown(shutdownCtx); err != nil {
588+
t.Logf("rest shutdown error: %v", err)
589+
}
578590
// gRPC server cleanup is handled by the context timeout
579591
}
580592

config_test.go

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package main
22

33
import (
4-
"os"
54
"testing"
65
)
76

@@ -35,12 +34,12 @@ func TestNewConfig(t *testing.T) {
3534
}
3635

3736
// Test environment variable override
38-
os.Setenv("STORAGE_TYPE", "couchdb")
39-
os.Setenv("COUCHDB_URL", "http://test:5984")
40-
os.Setenv("COUCHDB_DB", "testdb")
41-
os.Setenv("MONGODB_URI", "mongodb://test:27017")
42-
os.Setenv("MONGODB_DB", "testdb")
43-
os.Setenv("MONGODB_COLLECTION", "testcoll")
37+
t.Setenv("STORAGE_TYPE", "couchdb")
38+
t.Setenv("COUCHDB_URL", "http://test:5984")
39+
t.Setenv("COUCHDB_DB", "testdb")
40+
t.Setenv("MONGODB_URI", "mongodb://test:27017")
41+
t.Setenv("MONGODB_DB", "testdb")
42+
t.Setenv("MONGODB_COLLECTION", "testcoll")
4443

4544
config = NewConfig()
4645
if config.StorageType != "couchdb" {
@@ -62,13 +61,6 @@ func TestNewConfig(t *testing.T) {
6261
t.Errorf("Expected MongoDBCollection to be 'testcoll', got %s", config.MongoDBCollection)
6362
}
6463

65-
// Clean up environment variables
66-
os.Unsetenv("STORAGE_TYPE")
67-
os.Unsetenv("COUCHDB_URL")
68-
os.Unsetenv("COUCHDB_DB")
69-
os.Unsetenv("MONGODB_URI")
70-
os.Unsetenv("MONGODB_DB")
71-
os.Unsetenv("MONGODB_COLLECTION")
7264
}
7365

7466
func TestGetEnv(t *testing.T) {
@@ -79,12 +71,9 @@ func TestGetEnv(t *testing.T) {
7971
}
8072

8173
// Test environment variable override
82-
os.Setenv("TEST_VAR", "test_value")
74+
t.Setenv("TEST_VAR", "test_value")
8375
value = getEnv("TEST_VAR", "default")
8476
if value != "test_value" {
8577
t.Errorf("Expected 'test_value', got %s", value)
8678
}
87-
88-
// Clean up
89-
os.Unsetenv("TEST_VAR")
9079
}

grpc/server_test.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,12 @@ func TestStartError(t *testing.T) {
147147
if err != nil {
148148
t.Fatalf("Failed to create listener for test: %v", err)
149149
}
150-
defer listener.Close()
150+
defer func(listener net.Listener) {
151+
err := listener.Close()
152+
if err != nil {
153+
t.Errorf("Failed to close listener: %v", err)
154+
}
155+
}(listener)
151156

152157
// Now try to start a server on the same port, which should fail
153158
server := NewServer(mockStorage, 8082)
@@ -201,7 +206,10 @@ func TestGetNote(t *testing.T) {
201206

202207
// Create a note
203208
originalNote := model.NewNote("Test Title", "Test Content")
204-
mockStorage.Create(ctx, originalNote)
209+
err := mockStorage.Create(ctx, originalNote)
210+
if err != nil {
211+
return
212+
}
205213

206214
// Test getting an existing note
207215
note, err := server.GetNote(ctx, originalNote.ID)
@@ -237,8 +245,14 @@ func TestGetAllNotes(t *testing.T) {
237245
// Create some notes
238246
note1 := model.NewNote("Title 1", "Content 1")
239247
note2 := model.NewNote("Title 2", "Content 2")
240-
mockStorage.Create(ctx, note1)
241-
mockStorage.Create(ctx, note2)
248+
err1 := mockStorage.Create(ctx, note1)
249+
if err1 != nil {
250+
return
251+
}
252+
err2 := mockStorage.Create(ctx, note2)
253+
if err2 != nil {
254+
return
255+
}
242256

243257
// Get all notes
244258
notes, err := server.GetAllNotes(ctx)
@@ -274,7 +288,10 @@ func TestUpdateNote(t *testing.T) {
274288

275289
// Create a note
276290
originalNote := model.NewNote("Original Title", "Original Content")
277-
mockStorage.Create(ctx, originalNote)
291+
err := mockStorage.Create(ctx, originalNote)
292+
if err != nil {
293+
return
294+
}
278295

279296
// Update the note
280297
updatedNote, err := server.UpdateNote(ctx, originalNote.ID, "Updated Title", "Updated Content")
@@ -313,7 +330,10 @@ func TestDeleteNote(t *testing.T) {
313330

314331
// Create a note
315332
note := model.NewNote("Test Title", "Test Content")
316-
mockStorage.Create(ctx, note)
333+
errc := mockStorage.Create(ctx, note)
334+
if errc != nil {
335+
return
336+
}
317337

318338
// Delete the note
319339
err := server.DeleteNote(ctx, note.ID)

main_test.go

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ func acquireLock() (*os.File, error) {
5252
f, err := os.OpenFile(lockFile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
5353
if err == nil {
5454
// Write our PID to the lock file
55-
fmt.Fprintf(f, "%d", os.Getpid())
55+
_, errF := fmt.Fprintf(f, "%d", os.Getpid())
56+
if errF != nil {
57+
return nil, errF
58+
}
5659
return f, nil
5760
}
5861
// Lock file exists, wait and retry
@@ -64,8 +67,14 @@ func acquireLock() (*os.File, error) {
6467
// releaseLock releases the file-based lock
6568
func releaseLock(f *os.File) {
6669
if f != nil {
67-
f.Close()
68-
os.Remove(getLockFilePath())
70+
errC := f.Close()
71+
if errC != nil {
72+
return
73+
}
74+
errR := os.Remove(getLockFilePath())
75+
if errR != nil {
76+
return
77+
}
6978
}
7079
}
7180

@@ -186,14 +195,17 @@ func TestMain(m *testing.M) {
186195
}
187196

188197
// Clean up state file
189-
os.Remove(getStateFilePath())
198+
errR := os.Remove(getStateFilePath())
199+
if errR != nil {
200+
return
201+
}
190202
}
191203

192204
os.Exit(code)
193205
}
194206

195207
func startSharedMongoDBContainer(ctx context.Context) error {
196-
container, err := mongodb.RunContainer(ctx, testcontainers.WithImage("mongo:7.0.25-jammy"))
208+
container, err := mongodb.Run(ctx, "mongo:7.0.25-jammy")
197209
if err != nil {
198210
return fmt.Errorf("failed to start MongoDB container: %w", err)
199211
}
@@ -335,41 +347,3 @@ type MyLogConsumer struct{}
335347
func (c *MyLogConsumer) Accept(log testcontainers.Log) {
336348
fmt.Printf("Log: %s\n", string(log.Content))
337349
}
338-
339-
// startCouchDBContainer starts a CouchDB container and returns the container and connection URL
340-
func startCouchDBContainer(ctx context.Context) (testcontainers.Container, string, error) {
341-
consumer := &MyLogConsumer{}
342-
req := testcontainers.ContainerRequest{
343-
Image: "couchdb:3.4.3",
344-
ExposedPorts: []string{"5984/tcp"},
345-
WaitingFor: wait.ForListeningPort("5984/tcp"),
346-
Env: map[string]string{
347-
"COUCHDB_USER": "admin",
348-
"COUCHDB_PASSWORD": "password",
349-
},
350-
LogConsumerCfg: &testcontainers.LogConsumerConfig{
351-
Consumers: []testcontainers.LogConsumer{
352-
consumer,
353-
},
354-
},
355-
}
356-
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
357-
ContainerRequest: req,
358-
Started: true,
359-
})
360-
if err != nil {
361-
return nil, "", err
362-
}
363-
host, err := container.Host(ctx)
364-
if err != nil {
365-
container.Terminate(ctx)
366-
return nil, "", err
367-
}
368-
port, err := container.MappedPort(ctx, "5984")
369-
if err != nil {
370-
container.Terminate(ctx)
371-
return nil, "", err
372-
}
373-
url := fmt.Sprintf("http://admin:password@%s:%s", host, port.Port())
374-
return container, url, nil
375-
}

rest/handlers.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ func (h *Handler) RegisterRoutes(r chi.Router) {
7272
// This endpoint can be used by load balancers or monitoring tools to check if the service is healthy.
7373
func (h *Handler) handleHealth(w http.ResponseWriter, r *http.Request) {
7474
w.WriteHeader(http.StatusOK) // Set the status code to 200 OK
75-
w.Write([]byte("OK")) // Write a simple "OK" response
75+
if _, err := w.Write([]byte("OK")); err != nil {
76+
_ = err // cannot change status code; ignore write error
77+
}
7678
}
7779

7880
// getAllNotes handles GET /api/notes.

rest/handlers_test.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,14 @@ func TestGetAllNotes(t *testing.T) {
227227
// Add some notes to the storage
228228
note1 := model.NewNote("Title 1", "Content 1")
229229
note2 := model.NewNote("Title 2", "Content 2")
230-
mockStorage.Create(context.Background(), note1)
231-
mockStorage.Create(context.Background(), note2)
230+
err1 := mockStorage.Create(context.Background(), note1)
231+
if err1 != nil {
232+
return
233+
}
234+
err2 := mockStorage.Create(context.Background(), note2)
235+
if err2 != nil {
236+
return
237+
}
232238

233239
req := setupTestRequest("GET", "/api/notes", "")
234240
w := httptest.NewRecorder()
@@ -279,7 +285,10 @@ func TestGetNote(t *testing.T) {
279285

280286
// Add a note to the storage with a valid ID
281287
note := &model.Note{ID: "testid123", Title: "Test Title", Content: "Test Content"}
282-
mockStorage.Create(context.Background(), note)
288+
errC := mockStorage.Create(context.Background(), note)
289+
if errC != nil {
290+
return
291+
}
283292

284293
req := setupTestRequest("GET", "/api/notes/"+note.ID, "")
285294
chiCtx := chi.NewRouteContext()
@@ -358,7 +367,10 @@ func TestUpdateNote(t *testing.T) {
358367

359368
// Add a note to the storage with a valid ID
360369
note := &model.Note{ID: "testid123", Title: "Original Title", Content: "Original Content"}
361-
mockStorage.Create(context.Background(), note)
370+
errC := mockStorage.Create(context.Background(), note)
371+
if errC != nil {
372+
return
373+
}
362374

363375
reqBody := `{"title":"Updated Title","content":"Updated Content"}`
364376
req := setupTestRequest("PUT", "/api/notes/"+note.ID, reqBody)
@@ -467,7 +479,10 @@ func TestDeleteNote(t *testing.T) {
467479

468480
// Add a note to the storage with a valid ID
469481
note := &model.Note{ID: "testid123", Title: "Test Title", Content: "Test Content"}
470-
mockStorage.Create(context.Background(), note)
482+
errC := mockStorage.Create(context.Background(), note)
483+
if errC != nil {
484+
return
485+
}
471486

472487
req := setupTestRequest("DELETE", "/api/notes/"+note.ID, "")
473488
chiCtx := chi.NewRouteContext()

0 commit comments

Comments
 (0)