From c3c9a4c855dcd7e6639e12f696322e669c2b67bd Mon Sep 17 00:00:00 2001 From: Patrick Erber Date: Wed, 30 Apr 2025 20:48:12 +0200 Subject: [PATCH 1/7] chore: add github workflow & golang linting --- .github/workflows/ci.yml | 54 ++++++++++++++++++++++++++++++++++++++++ .golangci-lint | 14 +++++++++++ 2 files changed, 68 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .golangci-lint diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..042296cc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +on: + pull_request: + branches: + - main + +jobs: + lint-go: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '1.23.4' + + - name: Install dependencies + run: go mod download + + - name: Lint with golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + args: --timeout=5m + + test-go: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '1.23.4' + + - name: Install dependencies + run: go mod download + + - name: Run tests with coverage + run: | + mkdir -p coverage + go test -v -coverprofile=coverage/coverage.out ./... + go tool cover -func=coverage/coverage.out + + - name: Upload coverage report as artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage/coverage.out + diff --git a/.golangci-lint b/.golangci-lint new file mode 100644 index 00000000..5bfb255a --- /dev/null +++ b/.golangci-lint @@ -0,0 +1,14 @@ +run: + timeout: 5m +issues: + exclude-use-default: false +linters: + enable: + - govet + - gofmt + - goimports + - unused + - deadcode + - gocritic + - staticcheck + - structcheck \ No newline at end of file From 61f2504469c780267fe777eb6c4e3c4aa9760cae Mon Sep 17 00:00:00 2001 From: Patrick Erber Date: Wed, 30 Apr 2025 21:00:04 +0200 Subject: [PATCH 2/7] fix: go linting issues --- internal/core/assets/assets_service_test.go | 20 ++++++++--- internal/core/auth/auth_service.go | 4 ++- internal/core/tree/page_store.go | 3 +- internal/core/tree/tree_service_test.go | 39 +++++++++++++++------ internal/http/router_test.go | 16 ++++++--- 5 files changed, 59 insertions(+), 23 deletions(-) diff --git a/internal/core/assets/assets_service_test.go b/internal/core/assets/assets_service_test.go index d5496f7f..623e6884 100644 --- a/internal/core/assets/assets_service_test.go +++ b/internal/core/assets/assets_service_test.go @@ -20,7 +20,9 @@ func createMultipartFile(filename string, content []byte) (multipart.File, strin if err != nil { return nil, "", err } - part.Write(content) + if _, err := part.Write(content); err != nil { + return nil, "", err + } writer.Close() reader := multipart.NewReader(body, writer.Boundary()) @@ -43,8 +45,12 @@ func TestSaveAndListAsset(t *testing.T) { page := &tree.PageNode{Slug: "lonely-page", ID: "a7b3"} // Create index.md page pagePath := filepath.Join(tmp, "lonely-page") - os.MkdirAll(pagePath, 0755) - os.WriteFile(filepath.Join(pagePath, "index.md"), []byte("# Lonely Page"), 0644) + if err := os.MkdirAll(pagePath, 0755); err != nil { + t.Fatalf("failed to create test directory: %v", err) + } + if err := os.WriteFile(filepath.Join(pagePath, "index.md"), []byte("# Lonely Page"), 0644); err != nil { + t.Fatalf("failed to create test file: %v", err) + } service := NewAssetService(tmp, tree.NewSlugService()) file, name, err := createMultipartFile("my-image.png", []byte("hello image")) @@ -77,8 +83,12 @@ func TestDeletePageAndEnsureAllAssetsAreDeleted(t *testing.T) { page := &tree.PageNode{Slug: "lonely-page", ID: "a7b3"} // Create index.md page pagePath := filepath.Join(tmp, "lonely-page") - os.MkdirAll(pagePath, 0755) - os.WriteFile(filepath.Join(pagePath, "index.md"), []byte("# Lonely Page"), 0644) + if err := os.MkdirAll(pagePath, 0755); err != nil { + t.Fatalf("failed to create test directory: %v", err) + } + if err := os.WriteFile(filepath.Join(pagePath, "index.md"), []byte("# Lonely Page"), 0644); err != nil { + t.Fatalf("failed to create test file: %v", err) + } service := NewAssetService(tmp, tree.NewSlugService()) file, name, err := createMultipartFile("my-image.png", []byte("hello image")) diff --git a/internal/core/auth/auth_service.go b/internal/core/auth/auth_service.go index f8bffa25..6efa9b66 100644 --- a/internal/core/auth/auth_service.go +++ b/internal/core/auth/auth_service.go @@ -96,7 +96,9 @@ func (a *AuthService) RefreshToken(refreshToken string) (*AuthToken, error) { func generateJTI() string { b := make([]byte, 16) - rand.Read(b) + if _, err := rand.Read(b); err != nil { + return "" + } return hex.EncodeToString(b) } diff --git a/internal/core/tree/page_store.go b/internal/core/tree/page_store.go index 96c80e4a..e8249c14 100644 --- a/internal/core/tree/page_store.go +++ b/internal/core/tree/page_store.go @@ -246,8 +246,7 @@ func (f *PageStore) MovePage(entry *PageNode, parentEntry *PageNode) error { currentPath := path.Join(f.storageDir, GeneratePathFromPageNode(entry)) // Check if the entry is a file - src := currentPath - dest := parentPath + var src, dest string if _, err := os.Stat(currentPath + ".md"); err == nil { src = currentPath + ".md" dest = path.Join(parentPath, entry.Slug+".md") diff --git a/internal/core/tree/tree_service_test.go b/internal/core/tree/tree_service_test.go index 34912d21..51499ee6 100644 --- a/internal/core/tree/tree_service_test.go +++ b/internal/core/tree/tree_service_test.go @@ -415,17 +415,29 @@ func TestTreeService_FindPageByRoutePath_Success(t *testing.T) { _ = service.LoadTree() // Tree: root โ†’ architecture โ†’ project-a โ†’ specs - service.CreatePage(nil, "Architecture", "architecture") + _, err := service.CreatePage(nil, "Architecture", "architecture") + if err != nil { + t.Fatalf("CreatePage failed: %v", err) + } arch := service.GetTree().Children[0] - service.CreatePage(&arch.ID, "Project A", "project-a") + _, err = service.CreatePage(&arch.ID, "Project A", "project-a") + if err != nil { + t.Fatalf("CreatePage failed: %v", err) + } projectA := arch.Children[0] - service.CreatePage(&projectA.ID, "Specs", "specs") + _, err = service.CreatePage(&projectA.ID, "Specs", "specs") + if err != nil { + t.Fatalf("CreatePage failed: %v", err) + } // Datei anlegen specPath := filepath.Join(tmpDir, "root", "architecture", "project-a", "specs.md") - os.WriteFile(specPath, []byte("# Project A Specs"), 0644) + err = os.WriteFile(specPath, []byte("# Project A Specs"), 0644) + if err != nil { + t.Fatalf("Failed to write specs file: %v", err) + } // ๐Ÿ” Suche รผber RoutePath page, err := service.FindPageByRoutePath(service.GetTree().Children, "architecture/project-a/specs") @@ -443,10 +455,11 @@ func TestTreeService_FindPageByRoutePath_NotFound(t *testing.T) { service := NewTreeService(tmpDir) _ = service.LoadTree() - service.CreatePage(nil, "Top", "top") + if _, err := service.CreatePage(nil, "Top", "top"); err != nil { + t.Fatalf("CreatePage failed: %v", err) + } - _, err := service.FindPageByRoutePath(service.GetTree().Children, "top/missing") - if err == nil { + if _, err := service.FindPageByRoutePath(service.GetTree().Children, "top/missing"); err == nil { t.Error("Expected error for non-existent nested path, got nil") } } @@ -456,11 +469,15 @@ func TestTreeService_FindPageByRoutePath_PartialMatch(t *testing.T) { service := NewTreeService(tmpDir) _ = service.LoadTree() - service.CreatePage(nil, "Docs", "docs") - service.CreatePage(nil, "API", "api") + if _, err := service.CreatePage(nil, "Docs", "docs"); err != nil { + t.Fatalf("CreatePage failed: %v", err) + } + + if _, err := service.CreatePage(nil, "API", "api"); err != nil { + t.Fatalf("CreatePage failed: %v", err) + } - _, err := service.FindPageByRoutePath(service.GetTree().Children, "docs/should-not-exist") - if err == nil { + if _, err := service.FindPageByRoutePath(service.GetTree().Children, "docs/should-not-exist"); err == nil { t.Error("Expected error for unmatched subpath") } } diff --git a/internal/http/router_test.go b/internal/http/router_test.go index f326a691..cf5b802b 100644 --- a/internal/http/router_test.go +++ b/internal/http/router_test.go @@ -954,7 +954,9 @@ func TestAssetEndpoints(t *testing.T) { writer := multipart.NewWriter(body) part, _ := writer.CreateFormFile("file", "testfile.txt") - part.Write([]byte("Hello, asset!")) + if _, err := part.Write([]byte("Hello, asset!")); err != nil { + t.Fatalf("Failed to write file: %v", err) + } writer.Close() req := httptest.NewRequest(http.MethodPost, "/api/pages/"+page.ID+"/assets", body) @@ -963,7 +965,9 @@ func TestAssetEndpoints(t *testing.T) { // Auth login := authenticatedRequest(t, router, http.MethodPost, "/api/auth/login", strings.NewReader(`{"identifier": "admin", "password": "admin"}`)) var loginResp map[string]string - json.Unmarshal(login.Body.Bytes(), &loginResp) + if err := json.Unmarshal(login.Body.Bytes(), &loginResp); err != nil { + t.Fatalf("Invalid login JSON: %v", err) + } token := loginResp["token"] req.Header.Set("Authorization", "Bearer "+token) @@ -988,7 +992,9 @@ func TestAssetEndpoints(t *testing.T) { t.Fatalf("Expected 200 OK on listing, got %d", listRec.Code) } var listResp map[string][]string - json.Unmarshal(listRec.Body.Bytes(), &listResp) + if err := json.Unmarshal(listRec.Body.Bytes(), &listResp); err != nil { + t.Fatalf("Invalid listing JSON: %v", err) + } if len(listResp["files"]) != 1 || listResp["files"][0] != "/assets/"+page.ID+"/testfile.txt" { t.Errorf("Expected file in listing, got: %v", listResp["files"]) } @@ -1002,7 +1008,9 @@ func TestAssetEndpoints(t *testing.T) { // Step 5: Verify asset is gone listRec2 := authenticatedRequest(t, router, http.MethodGet, "/api/pages/"+page.ID+"/assets", nil) var listResp2 map[string][]string - json.Unmarshal(listRec2.Body.Bytes(), &listResp2) + if err := json.Unmarshal(listRec2.Body.Bytes(), &listResp2); err != nil { + t.Fatalf("Invalid listing JSON: %v", err) + } if len(listResp2["files"]) != 0 { t.Errorf("Expected asset to be deleted, got: %v", listResp2["files"]) } From feda3b3f9360568158a416fceeaf29bffc2dbe03 Mon Sep 17 00:00:00 2001 From: Patrick Erber Date: Wed, 30 Apr 2025 21:11:18 +0200 Subject: [PATCH 3/7] feat: rename ci.yml to backend.yml --- .github/workflows/{ci.yml => backend.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{ci.yml => backend.yml} (98%) diff --git a/.github/workflows/ci.yml b/.github/workflows/backend.yml similarity index 98% rename from .github/workflows/ci.yml rename to .github/workflows/backend.yml index 042296cc..9a4ad019 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/backend.yml @@ -1,4 +1,4 @@ -name: CI +name: Backend CI on: pull_request: From 14484c0671d09d77f46546cbaefb414c665d6b4f Mon Sep 17 00:00:00 2001 From: Patrick Erber Date: Wed, 30 Apr 2025 21:12:59 +0200 Subject: [PATCH 4/7] feat: enable golang checks when pushing to main branch --- .github/workflows/backend.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 9a4ad019..abee0e03 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -5,6 +5,10 @@ on: branches: - main + push: + branches: + - main + jobs: lint-go: runs-on: ubuntu-latest From c0df6ab2326cd01170fcd87e4818e683b4485403 Mon Sep 17 00:00:00 2001 From: Patrick Erber Date: Wed, 30 Apr 2025 21:24:02 +0200 Subject: [PATCH 5/7] test: TestAssetEndpointTest - type issue --- internal/http/router_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/http/router_test.go b/internal/http/router_test.go index cf5b802b..c3e0f14a 100644 --- a/internal/http/router_test.go +++ b/internal/http/router_test.go @@ -962,13 +962,13 @@ func TestAssetEndpoints(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/api/pages/"+page.ID+"/assets", body) req.Header.Set("Content-Type", writer.FormDataContentType()) - // Auth + // Get Token for authentication login := authenticatedRequest(t, router, http.MethodPost, "/api/auth/login", strings.NewReader(`{"identifier": "admin", "password": "admin"}`)) - var loginResp map[string]string + var loginResp map[string]interface{} if err := json.Unmarshal(login.Body.Bytes(), &loginResp); err != nil { t.Fatalf("Invalid login JSON: %v", err) } - token := loginResp["token"] + token := loginResp["token"].(string) req.Header.Set("Authorization", "Bearer "+token) rec := httptest.NewRecorder() From b2a80f10f00e1a6bbf9d3c022d6bd8d0279eabb0 Mon Sep 17 00:00:00 2001 From: Patrick Erber Date: Wed, 30 Apr 2025 21:32:06 +0200 Subject: [PATCH 6/7] chore: enable frontend checks --- .github/workflows/frontend.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/frontend.yml diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml new file mode 100644 index 00000000..4188877a --- /dev/null +++ b/.github/workflows/frontend.yml @@ -0,0 +1,34 @@ +name: Frontend CI + +on: + pull_request: + branches: + - main + push: + branches: [main] + +jobs: + lint-frontend: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ui/leafwiki-ui + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Lint with ESLint + run: npm run lint + + - name: Run Prettier check + run: npx prettier --check src/** \ No newline at end of file From 8d62360d6d8f51e24f80630c66991903362c3161 Mon Sep 17 00:00:00 2001 From: Patrick Erber Date: Wed, 30 Apr 2025 21:39:39 +0200 Subject: [PATCH 7/7] chore: disable frontend linting for now --- .github/workflows/frontend.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 4188877a..57e22a40 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -27,8 +27,11 @@ jobs: - name: Install dependencies run: npm ci - - name: Lint with ESLint - run: npm run lint +# Disable Liniting for now! +# Because we have many linting errors in the codebase which we need to fix and to test. +# f.g. useEffect with missing dependencies - This could be a problem when we don't test it, before we resolve the linting errors. +# - name: Lint with ESLint +# run: npm run lint - name: Run Prettier check run: npx prettier --check src/** \ No newline at end of file