diff --git a/.golangci.yml b/.golangci.yml index 98267a7..406b01f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -10,7 +10,7 @@ run: linters: default: none enable: - # Default tier + # Default tier — must-have for any Go project - errcheck - govet - staticcheck @@ -21,11 +21,18 @@ linters: - errorlint - gosec - sqlclosecheck + - nilerr + - bodyclose + - durationcheck + - errchkjson + - exhaustive + - forcetypeassert # Code quality - depguard - dogsled - dupl + - dupword - errname - funlen - gocheckcompilerdirectives @@ -35,20 +42,24 @@ linters: - godox - goprintffuncname - lll + - mirror - misspell - nakedret + - predeclared - revive - testifylint - unconvert - unparam - usestdlibvars + - wastedassign - whitespace # Complexity - gocognit + - nestif # Note: copyloopvar, intrange, modernize, perfsprint require Go 1.22+ - # They will be enabled when Go version constraint is lifted + # They will be enabled when Go version constraint is lifted. settings: depguard: @@ -61,6 +72,8 @@ linters: desc: Deprecated since Go 1.16. Use io and os packages instead. - pkg: "github.com/instana/testify" desc: Use github.com/stretchr/testify instead. + exhaustive: + default-signifies-exhaustive: true dupl: threshold: 100 funlen: @@ -82,17 +95,15 @@ linters: disabled-checks: - dupImport - hugeParam - - rangeValCopy - - ifElseChain - - octalLiteral + - rangeValCopy # keychainbreaker structs are large by design + - unnamedResult # crypto functions returning (key, iv) are clear without names - whyNoLint - - singleCaseSwitch - - exitAfterDefer - - commentedOutCode lll: line-length: 140 gocognit: min-complexity: 30 + nestif: + min-complexity: 5 godox: keywords: - FIXME @@ -103,17 +114,17 @@ linters: asserts: false gosec: excludes: - - G101 - - G104 - - G304 - - G401 - - G405 - - G501 - - G502 - - G505 - - G115 - - G117 - - G204 + - G101 # hardcoded credentials — false positives on const names + - G115 # integer overflow on conversion — false positives on safe narrowing + - G117 # struct field matches secret pattern — false positive on Password fields + - G204 # exec.Command with variable — required for macOS `security` command + - G304 # file inclusion via variable — required for dynamic browser paths + - G401 # weak crypto SHA1 — required for Chromium PBKDF2 key derivation + - G402 # TLS MinVersion — not applicable (no TLS in this tool) + - G405 # weak crypto DES — required for Firefox 3DES decryption + - G501 # blocklisted import crypto/md5 — not used, keep for safety + - G502 # blocklisted import crypto/des — required for Firefox decryption + - G505 # blocklisted import crypto/sha1 — required for PBKDF2 errcheck: check-type-assertions: true exclude-functions: @@ -127,22 +138,13 @@ linters: rules: - name: indent-error-flow - name: unexported-return - disabled: true - name: unused-parameter disabled: true - - name: package-comments - disabled: true - - name: exported - disabled: true staticcheck: checks: - "all" - - "-ST1000" - - "-ST1003" - - "-ST1016" - - "-ST1020" - - "-ST1021" - - "-ST1022" + - "-ST1000" # package comment — not a public library + - "-ST1003" # naming convention — allow platform-specific names exclusions: presets: @@ -157,7 +159,6 @@ linters: - funlen - gosec - errcheck - - testifylint - lll - source: "defer" linters: @@ -165,47 +166,12 @@ linters: - text: "SELECT" linters: - gosec - # Temporary: known issues in pre-refactoring code (will be removed during refactoring) - - text: "result 0 .* is always nil" - linters: - - unparam - - text: "result 0 .* is never used" - linters: - - unparam - - path: "browser/firefox/firefox.go" - text: "field .* is unused" - linters: - - unused - - path: "browserdata/sessionstorage/" - text: "is unused" - linters: - - unused - path: "cmd/hack-browser-data/main.go" linters: - lll - # Temporary: pre-refactoring code issues (all will be rewritten) - - path: "browserdata/" - linters: - - dupl - - gochecknoinits - - goconst - - lll - - path: "browser/firefox/" - linters: - - gocritic - - path: "crypto/" - linters: - - gocritic - path: "crypto/keyretriever/gcoredump_darwin.go" linters: - gocognit - # Temporary: new v2 extract files have no callers until Phase 8 wiring - - path: "browser/chromium/(source|decrypt|extract_.*)\\.go" - linters: - - unused - - path: "browser/firefox/(source|extract_.*)\\.go" - linters: - - unused formatters: enable: diff --git a/browser/browser_test.go b/browser/browser_test.go index 27500cd..c1529f6 100644 --- a/browser/browser_test.go +++ b/browser/browser_test.go @@ -21,7 +21,7 @@ func mkFile(t *testing.T, parts ...string) { func TestListBrowsers(t *testing.T) { list := ListBrowsers() - assert.True(t, len(list) > 0) + assert.NotEmpty(t, list) assert.True(t, sort.StringsAreSorted(list)) } diff --git a/browser/chromium/chromium_test.go b/browser/chromium/chromium_test.go index e4e1e82..8e399e6 100644 --- a/browser/chromium/chromium_test.go +++ b/browser/chromium/chromium_test.go @@ -399,7 +399,7 @@ func TestAcquireFiles(t *testing.T) { assert.Len(t, paths, len(cats)) for _, p := range paths { _, err := os.Stat(p) - assert.NoError(t, err, "acquired file should exist") + require.NoError(t, err, "acquired file should exist") } } diff --git a/browser/firefox/extract_history_test.go b/browser/firefox/extract_history_test.go index d6ee6fc..d3859a0 100644 --- a/browser/firefox/extract_history_test.go +++ b/browser/firefox/extract_history_test.go @@ -40,5 +40,5 @@ func TestExtractHistories_NullFields(t *testing.T) { require.NoError(t, err) require.Len(t, got, 1) assert.Equal(t, "https://null.test", got[0].URL) - assert.Equal(t, "", got[0].Title) + assert.Empty(t, got[0].Title) } diff --git a/crypto/asn1pbe_test.go b/crypto/asn1pbe_test.go index a45d231..0d802cb 100644 --- a/crypto/asn1pbe_test.go +++ b/crypto/asn1pbe_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -74,14 +75,14 @@ var ( func TestNewASN1PBE(t *testing.T) { for _, tc := range nssPBETestCases { nssRaw, err := hex.DecodeString(tc.RawHexPBE) - assert.Equal(t, nil, err) + require.NoError(t, err) pbe, err := NewASN1PBE(nssRaw) - assert.Equal(t, nil, err) + require.NoError(t, err) nssPBETC, ok := pbe.(nssPBE) - assert.Equal(t, true, ok) + assert.True(t, ok) assert.Equal(t, nssPBETC.Encrypted, tc.Encrypted) assert.Equal(t, nssPBETC.AlgoAttr.SaltAttr.EntrySalt, tc.GlobalSalt) - assert.Equal(t, nssPBETC.AlgoAttr.SaltAttr.Len, 20) + assert.Equal(t, 20, nssPBETC.AlgoAttr.SaltAttr.Len) assert.Equal(t, nssPBETC.AlgoAttr.ObjectIdentifier, tc.ObjectIdentifier) } } @@ -108,8 +109,8 @@ func TestNssPBE_Encrypt(t *testing.T) { }, } encrypted, err := nssPBETC.Encrypt(tc.GlobalSalt, tc.Plaintext) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(encrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, encrypted) assert.Equal(t, nssPBETC.Encrypted, encrypted) } } @@ -136,8 +137,8 @@ func TestNssPBE_Decrypt(t *testing.T) { }, } decrypted, err := nssPBETC.Decrypt(tc.GlobalSalt) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(decrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, decrypted) assert.Equal(t, pbePlaintext, decrypted) } } @@ -145,11 +146,11 @@ func TestNssPBE_Decrypt(t *testing.T) { func TestNewASN1PBE_MetaPBE(t *testing.T) { for _, tc := range metaPBETestCases { metaRaw, err := hex.DecodeString(tc.RawHexPBE) - assert.Equal(t, nil, err) + require.NoError(t, err) pbe, err := NewASN1PBE(metaRaw) - assert.Equal(t, nil, err) + require.NoError(t, err) metaPBETC, ok := pbe.(metaPBE) - assert.Equal(t, true, ok) + assert.True(t, ok) assert.Equal(t, metaPBETC.Encrypted, tc.Encrypted) assert.Equal(t, metaPBETC.AlgoAttr.Data.IVData.IV, tc.IV) assert.Equal(t, metaPBETC.AlgoAttr.Data.IVData.ObjectIdentifier, objWithSHA256AndAES) @@ -193,8 +194,8 @@ func TestMetaPBE_Encrypt(t *testing.T) { Encrypted: tc.Encrypted, } encrypted, err := metaPBETC.Encrypt(tc.GlobalSalt, tc.Plaintext) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(encrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, encrypted) assert.Equal(t, metaPBETC.Encrypted, encrypted) } } @@ -236,8 +237,8 @@ func TestMetaPBE_Decrypt(t *testing.T) { Encrypted: tc.Encrypted, } decrypted, err := metaPBETC.Decrypt(tc.GlobalSalt) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(decrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, decrypted) assert.Equal(t, pbePlaintext, decrypted) } } @@ -245,11 +246,11 @@ func TestMetaPBE_Decrypt(t *testing.T) { func TestNewASN1PBE_LoginPBE(t *testing.T) { for _, tc := range loginPBETestCases { loginRaw, err := hex.DecodeString(tc.RawHexPBE) - assert.Equal(t, nil, err) + require.NoError(t, err) pbe, err := NewASN1PBE(loginRaw) - assert.Equal(t, nil, err) + require.NoError(t, err) loginPBETC, ok := pbe.(loginPBE) - assert.Equal(t, true, ok) + assert.True(t, ok) assert.Equal(t, loginPBETC.Encrypted, tc.Encrypted) assert.Equal(t, loginPBETC.Data.IV, tc.IV) assert.Equal(t, loginPBETC.Data.ObjectIdentifier, objWithMD5AndDESCBC) @@ -270,8 +271,8 @@ func TestLoginPBE_Encrypt(t *testing.T) { Encrypted: tc.Encrypted, } encrypted, err := loginPBETC.Encrypt(tc.GlobalSalt, plainText) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(encrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, encrypted) assert.Equal(t, loginPBETC.Encrypted, encrypted) } } @@ -290,8 +291,8 @@ func TestLoginPBE_Decrypt(t *testing.T) { Encrypted: tc.Encrypted, } decrypted, err := loginPBETC.Decrypt(tc.GlobalSalt) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(decrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, decrypted) assert.Equal(t, pbePlaintext, decrypted) } } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index e1c596a..805466a 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const baseKey = "moond4rk" @@ -28,45 +29,45 @@ var ( func TestAES128CBCEncrypt(t *testing.T) { encrypted, err := AES128CBCEncrypt(aesKey, aesIV, plainText) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(encrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, encrypted) assert.Equal(t, aes128Ciphertext, fmt.Sprintf("%x", encrypted)) } func TestAES128CBCDecrypt(t *testing.T) { ciphertext, _ := hex.DecodeString(aes128Ciphertext) decrypted, err := AES128CBCDecrypt(aesKey, aesIV, ciphertext) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(decrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, decrypted) assert.Equal(t, plainText, decrypted) } func TestDES3Encrypt(t *testing.T) { encrypted, err := DES3Encrypt(des3Key, des3IV, plainText) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(encrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, encrypted) assert.Equal(t, des3Ciphertext, fmt.Sprintf("%x", encrypted)) } func TestDES3Decrypt(t *testing.T) { ciphertext, _ := hex.DecodeString(des3Ciphertext) decrypted, err := DES3Decrypt(des3Key, des3IV, ciphertext) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(decrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, decrypted) assert.Equal(t, plainText, decrypted) } func TestAESGCMEncrypt(t *testing.T) { encrypted, err := AESGCMEncrypt(aesKey, aesGCMNonce, plainText) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(encrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, encrypted) assert.Equal(t, aesGCMCiphertext, fmt.Sprintf("%x", encrypted)) } func TestAESGCMDecrypt(t *testing.T) { ciphertext, _ := hex.DecodeString(aesGCMCiphertext) decrypted, err := AESGCMDecrypt(aesKey, aesGCMNonce, ciphertext) - assert.Equal(t, nil, err) - assert.Equal(t, true, len(decrypted) > 0) + require.NoError(t, err) + assert.NotEmpty(t, decrypted) assert.Equal(t, plainText, decrypted) } diff --git a/crypto/keyretriever/keyretriever_test.go b/crypto/keyretriever/keyretriever_test.go index 3658d9f..d823331 100644 --- a/crypto/keyretriever/keyretriever_test.go +++ b/crypto/keyretriever/keyretriever_test.go @@ -43,7 +43,7 @@ func TestChainRetriever_AllFail(t *testing.T) { &mockRetriever{err: errors.New("second failed")}, ) key, err := chain.RetrieveKey("Chrome", "") - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, key) assert.Contains(t, err.Error(), "all retrievers failed") assert.Contains(t, err.Error(), "first failed") @@ -64,6 +64,6 @@ func TestChainRetriever_SkipEmptyKey(t *testing.T) { func TestChainRetriever_Empty(t *testing.T) { chain := NewChain() key, err := chain.RetrieveKey("Chrome", "") - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, key) } diff --git a/filemanager/copy_windows_test.go b/filemanager/copy_windows_test.go index ba65a23..6502e4f 100644 --- a/filemanager/copy_windows_test.go +++ b/filemanager/copy_windows_test.go @@ -25,12 +25,12 @@ func TestCopyLocked_ExclusiveLock(t *testing.T) { // Normal copy should fail err := copyFile(src, filepath.Join(dir, "normal_copy.db")) - assert.Error(t, err, "normal copy should fail on exclusively locked file") + require.Error(t, err, "normal copy should fail on exclusively locked file") // copyLocked should succeed via DuplicateHandle + FileMapping lockedDst := filepath.Join(dir, "locked_copy.db") err = copyLocked(src, lockedDst) - assert.NoError(t, err, "copyLocked should bypass exclusive lock") + require.NoError(t, err, "copyLocked should bypass exclusive lock") copied, err := os.ReadFile(lockedDst) require.NoError(t, err) @@ -62,7 +62,7 @@ func TestCopyLocked_WriteThenRead(t *testing.T) { // copyLocked should read the full content including appended data lockedDst := filepath.Join(dir, "modified_copy.db") copyErr := copyLocked(src, lockedDst) - assert.NoError(t, copyErr) + require.NoError(t, copyErr) copied, err := os.ReadFile(lockedDst) require.NoError(t, err) @@ -86,17 +86,17 @@ func TestCopyLocked_LargeFile(t *testing.T) { lockedDst := filepath.Join(dir, "large_copy.db") err := copyLocked(src, lockedDst) - assert.NoError(t, err) + require.NoError(t, err) copied, err := os.ReadFile(lockedDst) require.NoError(t, err) - assert.Equal(t, len(data), len(copied), "file sizes should match") + assert.Len(t, copied, len(data), "file sizes should match") assert.True(t, bytes.Equal(data, copied), "file content should match byte-for-byte") } func TestCopyLocked_FileNotFound(t *testing.T) { err := copyLocked("/nonexistent/file.db", filepath.Join(t.TempDir(), "dst.db")) - assert.Error(t, err) + require.Error(t, err) } func TestAcquire_FallbackToLocked(t *testing.T) { @@ -115,7 +115,7 @@ func TestAcquire_FallbackToLocked(t *testing.T) { dst := filepath.Join(session.TempDir(), "cookies.db") err = session.Acquire(src, dst, false) - assert.NoError(t, err, "Acquire should succeed via locked fallback") + require.NoError(t, err, "Acquire should succeed via locked fallback") copied, err := os.ReadFile(dst) require.NoError(t, err) @@ -136,7 +136,7 @@ func TestAcquire_NormalCopyWhenNotLocked(t *testing.T) { dst := filepath.Join(session.TempDir(), "unlocked.db") err = session.Acquire(src, dst, false) - assert.NoError(t, err) + require.NoError(t, err) copied, err := os.ReadFile(dst) require.NoError(t, err) diff --git a/filemanager/session_test.go b/filemanager/session_test.go index 7f1f678..5209dd0 100644 --- a/filemanager/session_test.go +++ b/filemanager/session_test.go @@ -42,7 +42,7 @@ func TestSession_Acquire_File(t *testing.T) { // Acquire it dst := filepath.Join(s.TempDir(), "Login Data") err = s.Acquire(srcFile, dst, false) - assert.NoError(t, err) + require.NoError(t, err) // Verify copy data, err := os.ReadFile(dst) @@ -63,7 +63,7 @@ func TestSession_Acquire_WAL(t *testing.T) { dst := filepath.Join(s.TempDir(), "Cookies") err = s.Acquire(srcFile, dst, false) - assert.NoError(t, err) + require.NoError(t, err) // Main file copied assert.FileExists(t, dst) @@ -85,7 +85,7 @@ func TestSession_Acquire_Dir(t *testing.T) { dst := filepath.Join(s.TempDir(), "leveldb") err = s.Acquire(srcDir, dst, true) - assert.NoError(t, err) + require.NoError(t, err) // Data file copied assert.FileExists(t, filepath.Join(dst, "000001.ldb")) @@ -99,5 +99,5 @@ func TestSession_Acquire_NotFound(t *testing.T) { dst := filepath.Join(s.TempDir(), "nope") err = s.Acquire("/nonexistent/file", dst, false) - assert.Error(t, err) + require.Error(t, err) } diff --git a/output/cookie_editor.go b/output/cookie_editor.go index 07aac32..1de4318 100644 --- a/output/cookie_editor.go +++ b/output/cookie_editor.go @@ -7,17 +7,27 @@ import ( "github.com/moond4rk/hackbrowserdata/types" ) -type cookieEditorFormatter struct{} +// cookieEditorFormatter outputs cookies in the CookieEditor browser extension +// format. Non-cookie categories fall back to standard JSON output. +type cookieEditorFormatter struct { + fallback *jsonFormatter +} func (f *cookieEditorFormatter) ext() string { return "json" } func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error { + if len(rows) == 0 { + return nil + } + // aggregate() guarantees all rows in a batch share the same type; + // check the first row to decide the format. + if _, ok := rows[0].entry.(types.CookieEntry); !ok { + return f.fallback.format(w, rows) + } + entries := make([]cookieEditorEntry, 0, len(rows)) for _, r := range rows { - c, ok := r.entry.(types.CookieEntry) - if !ok { - return nil // not cookies, skip - } + c, _ := r.entry.(types.CookieEntry) var expDate float64 if !c.ExpireAt.IsZero() { expDate = float64(c.ExpireAt.Unix()) diff --git a/output/formatter.go b/output/formatter.go index f178bb5..9306919 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -18,7 +18,7 @@ func newFormatter(name string) (formatter, error) { case "json": return &jsonFormatter{}, nil case "cookie-editor": - return &cookieEditorFormatter{}, nil + return &cookieEditorFormatter{fallback: &jsonFormatter{}}, nil default: return nil, fmt.Errorf("unsupported format: %s", name) } diff --git a/output/output_test.go b/output/output_test.go index c1f15b4..6300a56 100644 --- a/output/output_test.go +++ b/output/output_test.go @@ -65,10 +65,10 @@ func TestNew(t *testing.T) { t.Run(tt.format, func(t *testing.T) { out, err := NewWriter(t.TempDir(), tt.format) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, out) } else { - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, out) } }) @@ -142,7 +142,7 @@ func TestWrite_CSV_UTF8BOM(t *testing.T) { raw, err := os.ReadFile(filepath.Join(dir, "password.csv")) require.NoError(t, err) - require.True(t, len(raw) >= 3) + require.GreaterOrEqual(t, len(raw), 3) assert.Equal(t, utf8BOM, raw[:3], "CSV should start with UTF-8 BOM") } @@ -249,7 +249,7 @@ func TestWrite_CookieEditor(t *testing.T) { }, entries[0]) } -func TestWrite_CookieEditor_SkipsNonCookie(t *testing.T) { +func TestWrite_CookieEditor_FallbackJSON(t *testing.T) { dir := t.TempDir() out, err := NewWriter(dir, "cookie-editor") require.NoError(t, err) @@ -258,9 +258,9 @@ func TestWrite_CookieEditor_SkipsNonCookie(t *testing.T) { }) require.NoError(t, out.Write()) - // password file should not be created (cookie-editor only exports cookies) + // non-cookie categories fall back to standard JSON format _, err = os.Stat(filepath.Join(dir, "password.json")) - assert.True(t, os.IsNotExist(err)) + assert.False(t, os.IsNotExist(err), "password.json should be created via JSON fallback") } // --- File creation --- diff --git a/utils/fileutil/fileutil_test.go b/utils/fileutil/fileutil_test.go index 6e61d00..080e00c 100644 --- a/utils/fileutil/fileutil_test.go +++ b/utils/fileutil/fileutil_test.go @@ -29,7 +29,7 @@ func TestCompressDir(t *testing.T) { defer os.RemoveAll(tempDir) err := CompressDir(tempDir) - assert.NoError(t, err, "compressDir should not return an error") + require.NoError(t, err, "compressDir should not return an error") // Check if the zip file exists zipFile := filepath.Join(tempDir, filepath.Base(tempDir)+".zip") @@ -38,7 +38,7 @@ func TestCompressDir(t *testing.T) { t.Run("Directory Does Not Exist", func(t *testing.T) { err := CompressDir("/path/to/nonexistent/directory") - assert.Error(t, err, "should return an error for non-existent directory") + require.Error(t, err, "should return an error for non-existent directory") }) t.Run("Empty Directory", func(t *testing.T) { @@ -47,6 +47,6 @@ func TestCompressDir(t *testing.T) { defer os.RemoveAll(tempDir) err = CompressDir(tempDir) - assert.Error(t, err, "should return an error for an empty directory") + require.Error(t, err, "should return an error for an empty directory") }) } diff --git a/utils/sqliteutil/sqlite_test.go b/utils/sqliteutil/sqlite_test.go index 2fe63b1..55a2b8e 100644 --- a/utils/sqliteutil/sqlite_test.go +++ b/utils/sqliteutil/sqlite_test.go @@ -34,7 +34,7 @@ func TestQuerySQLite(t *testing.T) { return nil }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []string{"alpha", "beta", "gamma"}, names) } @@ -59,7 +59,7 @@ func TestQuerySQLite_JournalOff(t *testing.T) { values = append(values, v) return nil }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []string{"ok"}, values) } @@ -67,7 +67,7 @@ func TestQuerySQLite_FileNotFound(t *testing.T) { err := QuerySQLite("/nonexistent/path.db", false, "SELECT 1", func(rows *sql.Rows) error { return nil }) - assert.Error(t, err) + require.Error(t, err) } func TestQuerySQLite_BadQuery(t *testing.T) { @@ -83,7 +83,7 @@ func TestQuerySQLite_BadQuery(t *testing.T) { err = QuerySQLite(dbPath, false, "SELECT nonexistent FROM t", func(rows *sql.Rows) error { return nil }) - assert.Error(t, err) + require.Error(t, err) } func TestQueryRows(t *testing.T) { @@ -110,7 +110,7 @@ func TestQueryRows(t *testing.T) { return u, err }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []user{{"alice", 30}, {"bob", 25}}, users) } @@ -133,6 +133,6 @@ func TestQueryRows_Empty(t *testing.T) { return v, nil }) - assert.NoError(t, err) + require.NoError(t, err) assert.Nil(t, results) }