Skip to content

test: raise coverage of files below 90% with table-driven tests - #4274

Merged
aryanmehrotra merged 16 commits into
gofr-dev:developmentfrom
NitinKumar004:test/improve-coverage-table-driven
Sep 24, 2026
Merged

aryanmehrotra merged 16 commits into
gofr-dev:developmentfrom
NitinKumar004:test/improve-coverage-table-driven

Conversation

@NitinKumar004

@NitinKumar004 NitinKumar004 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description:

This PR adds tests only. It raises statement coverage for every file that was below 90%, across the main module and the pkg/gofr/datasource/* / metrics/exporters/gcp submodules.

  • Main module, measured the way CI measures it (-coverpkg=./pkg/gofr/..., generated mock_*.go excluded): 90.67% → 95.65% (+4.98pp).
  • Per-file view across the main module and the datasource/gcp submodules: 121 files were below 90% when measured per package. The table below uses that per-package basis, which is narrower than CI's, so some "before" values are lower than CI would report (for example container/container.go is 79.3% under CI's method, not 67.6%; grpc/middleware/common.go is 58.3%, not 50.0%).
  • Files still below 90% after this PR are listed below with the reason.
  • 117 test files change: +16,093 / −26 lines. No production code, mocks, go.mod or go.sum are touched.
  • After review, a number of weak or defect-pinning tests were removed or tightened (see "Changes after review"). That lowers a few files on purpose: opentsdb/observability.go 74.3%, cassandra/internal.go 81.2%, scylladb/internal.go 92.0%, kv-store/nats/interface.go 14.3% (the uncovered part is a test double shipped in that file).
  • The commits are grouped by area, 12 in total, so the PR can be reviewed commit by commit.

Changes after review

  • Dropped tests that pinned defects as correct: surrealdb incomplete-credentials Connect, opentsdb wrong-sentinel row, sql_mock order/argument checks, dgraph <nil> health log text.
  • Dropped the startup-hook-error row, which conflicts with fix(mcp): claim the MCP port with net.Listen instead of exiting from EnableMCP #3942 (that path will exit the process).
  • influxdb wrapper tests now use per-method recorders and fail if the wrong upstream method is called.
  • Removed tests that only padded coverage: metrics/exporters/exporter_test.go and the added case in testutil/error_test.go. Both target files were already at 100% under CI's measurement. Also removed the kv-store/nats mock-entry test and the cassandra/scylladb iterator tests, and collapsed the opentsdb span-name cases.
  • Tightened: gcs retry-exit immediacy (now time-bounded), eventhub commit, and three always-true Contains(stdout, "") checks in nats.
  • Lint: fixed the google importShadow and the kafka goconst regressions. The reviewer's golangci-lint 2.12.2 shows the same issue set as development for those packages.

How the tests are written

  • Table-driven, following each file's existing style. Expectations live in the table (expErr, expStatus, per-row setupMocks/mockCall), and test bodies don't branch on what to assert.
  • Placed next to the code they test. Tests for foo.go go into the existing foo_test.go. A new _test.go file is created only where that source file had none (28 new files, for example http/capture_test.go, kafka/conn_test.go, sftp/file_test.go).
  • Fully offline and deterministic. They reuse existing mocks and in-process fakes: gomock, sqlmock, redismock, miniredis, httptest, pstest, in-memory SFTP over net.Pipe, and loopback listeners on 127.0.0.1:0. There are no sleeps for timing and no real external services. Environment changes go through t.Setenv and working-directory changes through t.Chdir. Tests use t.Context() and package-level sentinel errors.
  • No known bugs locked in. Tests that asserted known-buggy production behavior were removed during review, so fixing those bugs later won't break CI (see "Possible production bugs found" below).

One test fix included

  • TestMQTT_SubscribeSuccess (existing test) could hang until Go's 10-minute test timeout, and it also does so on development.
  • Cause: its goroutine read subscriptions["test/topic"] before Subscribe had registered it, then sent on the zero value's nil channel.
  • Fix: it now waits for the registration. It passed 2,000 runs in a row, plus 300 with -race.

Verification (local, run the way CI runs it)

  • APP_ENV=test go test -covermode=atomic -coverpkg=./pkg/gofr/... ./pkg/gofr/... passes in 58 s. Every submodule's go test ./... passes, and none takes more than 60 s, well inside the 5-minute retry budget.
  • golangci-lint run --new-from-rev=origin/development reports 0 issues for the main module and every touched submodule. gofmt is clean.

Still below 90%, with the reason

File Before → after Reason
graphql.go, datasource/file/local_fs.go unchanged Left alone because open PRs #4268 and #4270 change their test files
pubsub/eventhub/{eventhub,helper,message}.go 44→68, 31→79, 0→29 Azure SDK partition clients have only unexported fields, with no seam to fake them
couchbase/wrappers.go 0→34 Thin wrappers over gocb's unexported connection manager; need a live cluster
file/{azure,ftp,gcs}/fs.go 86→88, 68→80, 61→69 Remaining statements are inside a hard-coded 1-minute retry ticker
redis/redis.go, kafka/helper.go, mqtt/default_client.go 73→83, 77→86, 76→87 Hard-coded 10 s retry timers, or a live broker
sftp/file.go 0→78 The JSON-object reader path is buggy (see below), so it isn't locked in by a test
dbresolver/circuit_breaker.go 88→88 The half-open branch can't be reached honestly (see below)
grpc.go, metrics/exporters/telemetry.go 72→89, 75→88 Fatalf/exit paths, and telemetry that posts to the real gofr.dev endpoint

Not covered on purpose: pkg/gofr/responder.go and pkg/gofr/traces/exporters/config.go contain only no-op declarations with no executable statements, so there is nothing meaningful to assert.

Possible production bugs found (not fixed here, since this PR is tests only; happy to open issues)

  1. datasource/oracle/oracle.go:88 builds the DSN with hard-coded local paths (libDir="/Users/zopdev/oracle-client/lib", wallet_location="/Users/zopdev/wallet").
  2. dbresolver/circuit_breaker.go allowRequest: CompareAndSwap(&openState, &halfOpenState) compares against a freshly allocated pointer, so an open breaker never moves to half-open after its timeout.
  3. container.go Kafka config: an invalid KAFKA_BATCH_SIZE, KAFKA_BATCH_BYTES or KAFKA_BATCH_TIMEOUT, or PUBSUB_OFFSET, logs "using default …" but passes the failed Atoi result (0) on.
  4. sftp createJSONObjectReader reopens the remote file name with os.Open on the local filesystem. Separately, peekJSONToken copies the decoder, so the JSON array reader has already consumed the stream.
  5. websocket.go: a response that can't be serialized isn't skipped, so an empty frame is written to the client. There is also a data race at websocket.go:47, which shows up in three existing tests under -race.
  6. Existing -race failures on development, which this PR doesn't touch: redis PubSub tests (redismock concurrency with monitorConnection), cmd/terminal spinner, nats TestClient_SubscribeWithHandler (flaky), and the gcs/ftp retry-state tests.

Breaking Changes (if applicable):

None. The PR adds tests only.

Additional Information:

No new dependencies. Every test helper used (redismock, miniredis, pstest, surrealcbor, pkg/sftp in-memory server, and so on) is already in the respective go.mod.

Per-file coverage for every file that was below 90% (click to expand)
File Before After
ai/llm/wire.go 83.6% 100.0%
cmd/terminal/progress.go 87.2% 100.0%
container/container.go 67.6% 95.0%
container/metrics_exporter.go 87.0% 100.0%
context.go 75.0% 100.0%
crud_handlers.go 89.8% 100.0%
datasource/arangodb/arango.go 87.7% 98.1%
datasource/arangodb/arango_document.go 82.5% 100.0%
datasource/arangodb/arango_graph.go 87.8% 100.0%
datasource/arangodb/logger.go 86.7% 100.0%
datasource/cassandra/internal.go 56.2% 96.9%
datasource/clickhouse/clickhouse.go 85.9% 95.8%
datasource/cloudsql/cloudsql.go 19.5% 92.7%
datasource/couchbase/couchbase.go 78.1% 93.4%
datasource/couchbase/logger.go 0.0% 100.0%
datasource/couchbase/wrappers.go 0.0% 34.3%
datasource/dbresolver/circuit_breaker.go 88.0% 88.0%
datasource/dbresolver/factory.go 73.9% 96.6%
datasource/dbresolver/resolver.go 89.5% 99.5%
datasource/dgraph/interfaces.go 16.7% 100.0%
datasource/dgraph/metrics.go 0.0% 100.0%
datasource/elasticsearch/elasticsearch.go 85.1% 100.0%
datasource/file/azure/fs.go 86.2% 87.9%
datasource/file/azure/storage_adapter.go 86.7% 93.9%
datasource/file/common_file.go 89.0% 100.0%
datasource/file/common_fs.go 86.4% 98.9%
datasource/file/ftp/fs.go 67.9% 80.4%
datasource/file/ftp/storage_adapter.go 88.2% 98.2%
datasource/file/gcs/fs.go 61.1% 69.4%
datasource/file/gcs/storage_adapter.go 89.8% 98.4%
datasource/file/interface.go 0.0% 100.0%
datasource/file/local_fs.go 72.6% 76.7%
datasource/file/logger.go 87.5% 100.0%
datasource/file/row_reader.go 84.2% 92.1%
datasource/file/s3/fs.go 88.1% 100.0%
datasource/file/s3/fs_dir.go 79.8% 99.1%
datasource/file/sftp/file.go 0.0% 78.0%
datasource/file/sftp/fs.go 82.9% 94.3%
datasource/influxdb/influxdb.go 71.9% 98.8%
datasource/influxdb/internal.go 0.0% 100.0%
datasource/influxdb/logger.go 0.0% 100.0%
datasource/influxdb/metrics_logger.go 60.0% 100.0%
datasource/kv-store/badger/badger.go 73.6% 94.4%
datasource/kv-store/dynamodb/logger.go 0.0% 100.0%
datasource/kv-store/nats/interface.go 14.3% 100.0%
datasource/kv-store/nats/logger.go 0.0% 100.0%
datasource/kv-store/nats/nats.go 53.0% 95.2%
datasource/opentsdb/observability.go 62.9% 100.0%
datasource/opentsdb/opentsdb.go 80.0% 98.8%
datasource/opentsdb/preprocess.go 67.1% 97.6%
datasource/opentsdb/response.go 78.8% 98.1%
datasource/oracle/oracle.go 87.2% 95.3%
datasource/pubsub/eventhub/eventhub.go 44.0% 68.3%
datasource/pubsub/eventhub/helper.go 31.0% 79.3%
datasource/pubsub/eventhub/message.go 0.0% 28.6%
datasource/pubsub/google/google.go 82.0% 96.6%
datasource/pubsub/kafka/conn.go 78.1% 100.0%
datasource/pubsub/kafka/health.go 88.5% 93.4%
datasource/pubsub/kafka/helper.go 77.1% 86.3%
datasource/pubsub/log.go 0.0% 100.0%
datasource/pubsub/mqtt/default_client.go 75.5% 86.8%
datasource/pubsub/nats/client.go 81.6% 100.0%
datasource/pubsub/nats/client_helper.go 81.7% 99.2%
datasource/pubsub/nats/config.go 72.7% 100.0%
datasource/pubsub/nats/connection_manager.go 85.0% 100.0%
datasource/pubsub/nats/pubsub_wrapper.go 0.0% 100.0%
datasource/pubsub/nats/stream_manager.go 79.5% 100.0%
datasource/pubsub/nats/subscription_manager.go 84.6% 100.0%
datasource/pubsub/sqs/sqs.go 77.4% 95.5%
datasource/redis/health.go 43.3% 100.0%
datasource/redis/pubsub.go 89.8% 92.4%
datasource/redis/redis.go 73.0% 82.5%
datasource/scylladb/internal.go 59.8% 97.7%
datasource/solr/logger.go 0.0% 100.0%
datasource/solr/solr.go 78.0% 99.1%
datasource/sql/sql.go 87.3% 97.1%
datasource/surrealdb/surrealdb.go 26.7% 97.5%
datasource/surrealdb/utils.go 0.0% 100.0%
datasource/surrealdb/wrapper.go 0.0% 100.0%
exporter.go 88.7% 98.1%
external_db.go 84.0% 99.2%
file/zip.go 89.6% 97.9%
gofr.go 88.7% 96.0%
graphql.go 81.5% 81.5%
grpc.go 72.0% 89.0%
grpc/middleware/common.go 50.0% 100.0%
grpc/middleware/oauth.go 80.6% 96.8%
http/capture.go 0.0% 100.0%
http/errors.go 75.7% 100.0%
http/form_data_binder.go 85.4% 98.9%
http/middleware/apikey_auth.go 87.5% 100.0%
http/middleware/basic_auth.go 88.9% 100.0%
http/middleware/errors.go 80.0% 100.0%
http/trie_router.go 85.8% 99.1%
http_server.go 80.0% 94.0%
logging/logger.go 88.5% 94.8%
metrics/exporters/gcp/gcp.go 87.8% 97.6%
metrics/exporters/provider.go 89.4% 93.6%
metrics/exporters/telemetry.go 75.0% 87.5%
metrics/register.go 87.3% 100.0%
migration/arango.go 65.8% 100.0%
migration/cassandra.go 73.3% 100.0%
migration/datasource.go 87.5% 100.0%
migration/dgraph.go 80.8% 98.1%
migration/elasticsearch.go 86.5% 100.0%
migration/opentsdb.go 75.5% 94.5%
migration/redis.go 87.7% 95.1%
migration/scylla_db.go 88.2% 100.0%
migration/sql.go 85.1% 93.9%
migration/surreal_db.go 80.0% 100.0%
rest.go 87.5% 90.0%
run.go 85.9% 92.9%
service/auth.go 83.3% 100.0%
service/logger.go 87.5% 100.0%
service/rate_limiter.go 86.0% 100.0%
service/rate_limiter_store.go 75.4% 96.9%
subscriber.go 61.0% 100.0%
telemetry.go 86.7% 93.3%
websocket.go 65.5% 96.6%

Checklist:

  • I have formatted my code using goimport and golangci-lint.
  • All new code is covered by unit tests.
  • This PR does not decrease the overall code coverage.
  • I have reviewed the code comments and documentation for clarity.

Table-driven tests for the root package: App setup and registration,
gRPC server wiring, websocket services, subscriber handling, CRUD
handlers, external datasource wiring, context helpers, run/shutdown,
exporter and telemetry.
Covers LLM/tool lookup, Kafka config validation paths (no network),
connection-from-context rows, the SQL mock container helpers and the
metrics cardinality fallback.
Adds trie-router edge cases, form-data binding for unusual kinds, the
response capture writer (new test file, none existed), HTTP error types,
and basic/API-key auth middleware paths.
Token-bucket edge cases, the Redis-backed store via redismock, the
denied path for every HTTP method, and auth option error paths.
…al, zip

Logger level/format paths, metrics registration, exporter/provider
build paths, LLM wire decoding, gRPC stream auth interceptors, terminal
progress, zip extraction errors and testutil.
SQL dialect/TLS/connection paths with sqlmock, Redis health and
PubSub stream paths with miniredis/redismock, and CommonFileSystem /
CommonFile / row reader behavior.
…e flake

Google Pub/Sub via the in-process pstest server, Kafka conn/helper/health
with gomock and loopback listeners, the MQTT default client, and the
shared pubsub log/message helpers.

Also fixes TestMQTT_SubscribeSuccess, which could hang until the 10m test
timeout: its goroutine read the subscription before Subscribe registered
it and sent on a nil channel. It now waits for the registration.
SQL, Redis, OpenTSDB, ArangoDB, Dgraph, SurrealDB, Cassandra, ScyllaDB
and Elasticsearch migrators, including lock-retry cancellation (no
sleeps) and deterministic filesystem error paths.
In-process fakes only: httptest for Azure/GCS, the existing goftp
server helper, an in-memory SFTP server over net.Pipe, and gomock for S3.
New fs_dir_test.go (s3) and file_test.go (sftp) where none existed.
NATS client/connection/stream/subscription managers and the JetStream
wrapper, SQS connect/config loading against an httptest endpoint with an
isolated AWS env, and the Event Hub paths reachable without a live
namespace.
…acle, dbresolver, dgraph, arangodb

gocql wrapper error paths via a closed session, dbresolver factory and
routing via SQLite, Cloud SQL dialer paths with fake credentials, and
httptest/in-process gRPC fakes for Dgraph and ArangoDB.
@NitinKumar004
NitinKumar004 force-pushed the test/improve-coverage-table-driven branch from f2c5f0b to b9428c9 Compare September 22, 2026 21:47
…r, elasticsearch, kv-stores, gcp

httptest fakes for SurrealDB (CBOR), InfluxDB, OpenTSDB, Solr and
Elasticsearch; Couchbase paths that need no live cluster; NATS/Badger/
DynamoDB kv-store paths; and the GCP exporter caching detector.
@NitinKumar004
NitinKumar004 force-pushed the test/improve-coverage-table-driven branch from b9428c9 to 87628ca Compare September 23, 2026 05:24

@aryanmehrotra aryanmehrotra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this in three passes so the whole diff actually got read rather than skimmed: datasource/pubsub + datasource/file (36 files), the other 17 datasource submodules (36 files), and everything outside datasource/ (47 files). Roughly 17,400 lines read, 43 mutations planted in production code, and every package run at -count=1, -race and -count=5 against both this branch and development.

The headline first, because it is the question worth asking of a coverage PR: these tests are functional, not written to move the number.

scope functional share of added lines mutations caught
pubsub + file ~98.1% 17 / 18
the other 17 datasources 92.8% 12 / 14
outside datasource/ 93.8% 10 / 11 conclusive

I went in expecting mock round-trips — a fake returning what it was told to return — and that is not what this is. 11 of 17 datasource packages get a real in-process fake: an httptest CBOR RPC server driving the actual SurrealDB client, a real gRPC api.DgraphServer, a hand-rolled NATS TCP handshake, a real embedded BadgerDB, miniredis, sqlmock plus a real SQLite file, pstest.NewServer for GCP Pub/Sub, sftp.NewRequestServer over net.Pipe() with no sockets at all. Zero t.Skip, zero os.Getenv, zero reachable-network dependencies across all 119 files — I ran the whole thing with no Docker and nothing hung or skipped.

Some of it is genuinely excellent:

  • surrealdb 22.2% → 92.3% with a real protocol fake. Best test engineering in the PR.
  • Test_APIKeyAuthMiddleware gets deleted and rewritten. It was literally t.Logf("Test_APIKeyAuthMiddleware"). Replacing a vacuous test with a real 3-row table is the opposite of coverage farming and I want to say so explicitly.
  • dgraph/interfaces_test.go asserts the request dgo builds (RespFormat: api.Request_RDF vs _JSON, ReadOnly, BestEffort). That is exactly the right assertion for a thin wrapper, and it is why my QueryRDF→Query mutation died.
  • context_test.go:734 writes a sentinel after the call under test and asserts the first message received, using in-order delivery to prove nothing was written. Deterministic, no sleep.
  • mqtt_test.go:238-256 fixes a pre-existing -race failure. On development pubsub/mqtt -race fails two tests; here it fails one.
  • assert.Equal(t, tt.expStderr == "", stderr == "") in the nats tests turns "expected no output" from an always-true Contains(x, "") into a real assertion that also catches spurious logging. I'd steal that.
  • The PR body lists six production bugs found and declines to lock any in. Two I independently confirmed.

So this is good work. I'm requesting changes because a handful of specific items need fixing before it lands, and at this size they will not survive being left as optional comments.


1. Blocking: run_test.go:245 will os.Exit(1) the whole pkg/gofr test binary

TestApp_Run_StartupHookFailure, subtest "hook error aborts startup", calls app.Run() with an OnStart hook returning an error and expects Run to return — the comment says "Run must return on its own".

PR #3942 changes exactly that path: handleStartupHooks returns startupFailed → finishAbandonedStartup → abortStartup() → os.Exit(exitCodeStartupFailed), because app := New() never sets the a.exit seam.

I merged the two locally and ran it: the test binary exits silently with no --- FAIL marker, killing every test after it in the package. Only the context.Canceled subtest survives, because that path returns startupCanceled and doesn't exit.

Neither PR's CI can see this — each is green alone. Whichever lands second turns pkg/gofr red and truncates the suite.

Smallest fix on this side is app.exit = func(int) {} in that subtest. Happy to carry it on #3942 instead if you'd rather — just tell me which, so we don't both do it or neither.

2. Three tests pin defects as correct behavior

surrealdb/surrealdb_test.go:1196-1204 — this is the expensive one. Connect() calls authenticateCredentials, and on errInvalidCredentialsConfig (username set, password empty — returned un-logged at surrealdb.go:192-194) it does a bare return. c.db was already assigned at surrealdb.go:151, so the client reports itself connected with unauthenticated credentials and not one log line is emitted. The new case asserts expConnDone: true with only a Debugf expectation — certifying that as correct.

I added c.logError("failed to authenticate with SurrealDB", err); c.db = nil to that branch and got --- FAIL: Test_Connect/incomplete_credentials_abort_connection_setup. So the next person to fix this has to argue with a test. Could you drop or invert that case, and ideally file the Connect() bug separately?

opentsdb/opentsdb_test.go:725-731 — opentsdb.go:204 returns errInvalidResponseType ("invalid response type") when the datapoints argument has the wrong type. Wrong sentinel, pre-existing. The case {"invalid datapoints type", datas: "not-datapoints", expErr: errInvalidResponseType} freezes it — changing :204 to errInvalidParam fails the test.

container/mockcontainer_test.go:337 — cements three defects in sql_mock.go: :79 assigns the canned response before validating query and args (the test's own comment says so, then asserts it); :83 passes (query, expectedText) to "expected query: %q, actual query: %q" — arguments swapped; :94 does the same on args, with %d for an any. The test uses gomock.Any() for both format arguments, so it pins the wording while being structurally unable to catch the swap.

3. influxdb_test.go:885-1038 — 154 lines that cannot catch a wrapper bug

fakeBucketsAPI returns f.bucket from six different methods, so the wrapper can call the wrong upstream and nothing notices. Two mutations survived:

  • internal.go:60 FindBucketByID → b.api.FindBucketByName(...) → test passes
  • internal.go:28 CreateOrganizationWithName → a.api.FindOrganizationByName(...) → test passes

That's 31% of the package's added lines, and influxdb is the biggest coverage jump in its scope (+19.2pp) — so this is the one place where the number moved without assertions behind it. dgraph/interfaces_test.go already has the right pattern: per-method recorders (lastQuery/lastAlter/lastLogin). Same shape here would make these 154 lines real.

4. Two new lint regressions

golangci-lint 2.12.2 over both trees: development 23 issues, this branch 27.

  • pubsub/google/google_test.go:588 and :614 — importShadow: shadow of imported package 'status' (gocritic). The new import "google.golang.org/grpc/status" at google_test.go:22 is shadowed by two pre-existing status, details := g.getWriterDetails() lines. Renaming the import alias fixes both.
  • pubsub/kafka/health.go — two goconst findings (isController, status), collateral from the new test pushing package-wide counts over the threshold.

Separately, in the datasource submodules there are 16 new goconst findings, all on production lines this PR never touches (surrealdb :253,464,500,541,578,615, sql :477,481,551, dgraph :302, dbresolver :517, couchbase :496, and others), for the same reason — goconst counts literals in _test.go. CI won't fail on any of this (only-new-issues: true), but the next PR that touches those lines inherits them. Worth considering ignore-tests for goconst in .golangci.yml as a follow-up.

5. Tests to drop or tighten

file:line Problem
gcs/fs_test.go:256 TestStartRetryConnect_ExitsImmediately Doesn't check immediacy. I deleted the IsRetryDisabled() early return at gcs/fs.go:89 and it still passed — just took 61.7s instead of 13.7s, rescued by the duplicate check at :97. Needs a deadline or a completion channel.
eventhub/message_test.go:13 TestMessage_Commit Both table rows execute the identical line (message.go:25-27's else branch never reads a.event), and the only assertion is an exact Debugf string.
metrics/exporters/exporter_test.go (35 ln) Mutation Config{AppName, AppVersion} → Config{} survived the whole package suite. Prometheus() is a 3-line deprecated shim already covered by provider_test.go.
testutil/error_test.go (17 ln) Both this and the above target files that were already at 100% under CI's measurement — see §6.
kv-store/nats/nats_test.go:705 Tests MockKeyValueEntry, a test double that ships in the production file interface.go:38-44. Seven stubs returning literals; the test asserts those literals.
opentsdb/observability_test.go:65 Nine table cases differing only by a span name that is never asserted. Both span-name mutations survived. One case would prove the nil-tracer branch.
cassandra/internal_test.go:84, scylladb/internal_test.go:93 Assert only zero values; gutting the three iterator methods to constants leaves both green. Between them that's half of cassandra's added lines.
nats/client_helper_test.go "consumer info unavailable skips deletion" Its only written assertion is assert.Contains(t, stdout, "") — always true. It survives on the strict gomock having no DeleteConsumer expectation.

6. The PR body's coverage baseline isn't CI's

The per-file table uses per-package coverage; CI uses whole-module -coverpkg (.github/workflows/go.yml:60,76). Reproducing CI's method, several "before" numbers are much higher, and two files the body says it rescued were already at 100%:

file body CI's method
metrics/exporters/exporter.go 0.0% → 100% 100% → 100%
testutil/error.go 0.0% → 100% 100% → 100%
container/container.go 67.6% → 95.0% 79.3% → 95.0%
grpc/middleware/common.go 50.0% → 100% 58.3% → 100%

Not dishonest, but "84.9% → 95.4%, 121 files below 90% → 16" is computed on a narrower basis than the gate, and those two 100%→100% files are exactly where the padding sits. A footnote would fix it. (Under CI's method the whole main module goes 90.72% → 95.68%, which is still a real +4.97pp.)

Also unlisted and still 0%: pkg/gofr/responder.go and pkg/gofr/traces/exporters/config.go. Trivial, but the table claims completeness.

One measurement note in your favour: arangodb's +2.3pp is an artifact, not weak work. Its coverage profile carries 592 functions from mock_*.go, 517 of them at 0.0% — ~6,000 lines of generated gomock in non-test files dominating the denominator. All 8 arangodb tests are functional and one is mutation-verified. That package will never report a sane number until the generated mocks move behind a build tag or into _test.go.

7. Please drop the AI-attribution footer

The body carries a 🤖 Generated with [Claude Code] line. This repo's convention is that merged history attributes only the human author.

8. On the size

119 files and 16,168 lines is not reviewable in one pass, and the #3942 landmine is the proof: it's invisible to CI, invisible to a diff skim, and only surfaces if someone merges the branch and runs the suite. At this size nobody does that — it took three parallel passes here to find it.

What a reviewer can verify cheaply: zero production files changed (I confirmed — git diff --name-only | grep -v _test.go$ is empty, 119 test files, 0 other), gofmt clean, CI green, coverage delta. What they must take on trust is whether ~390 test functions assert anything.

If you're open to splitting, the seam I'd cut along is the Go module boundary, which is already how CI shards it and roughly how your 12 commits group:

  1. main module — 47 files / 4,324 lines, and where all the collision surfaces live
  2. datasource/pubsub + datasource/file — 36 files / 5,099 lines
  3. the remaining 17 datasource submodules — 36 files / 6,745 lines

To be clear: split for reviewability, don't prune for quality. Cutting to "only the functional tests" would discard about 2% of the lines and isn't worth it.


Things this PR found that aren't its job to fix

Worth separate issues:

  • CommonFileSystem.{connected,disableRetry} (file/common_fs.go:630-648) is an unsynchronized data race against the background retry goroutine — it's why file/ftp and file/gcs fail under -race on development today. TestCommonFileSystem_RetryAndConnectedState exercises those accessors single-threaded, which a reviewer could read as "covered".
  • nats/stream_manager.go:56 matches strings.Contains(err.Error(), "stream name already in use") rather than errors.Is(err, jetstream.ErrStreamNameAlreadyInUse). Verified against the pin — nats.go@v1.53.1/jetstream/errors.go:107 defines that exact description, so it works today, but a minor bump that rewords it silently turns a tolerated condition into a hard failure. The new test at stream_manager_test.go:268 cements the fragile match.
  • initTest / setupDB call ctrl.Finish() inside themselves in cassandra, scylladb and dgraph, so it runs before any expectation is registered. go.uber.org/mock@v0.6.0/gomock/controller.go:268-273 then short-circuits the t.Cleanup finish, and "missing call(s)" is never reported in those packages — every .Times(1) guards only against a wrong call, never against no call.
  • dgraph.go:300-301 logs Error("dgraph health check failed: ", err) with err == nil on the empty-response path, so the line reads ... : <nil>. TestClient_HealthCheck now pins that.
  • couchbase/wrappers_test.go:66-68 asserts a gocb v2.12.4 error string; verified there's no sentinel behind it (cluster_query.go:249 is an inline errors.New), so the string is the only handle — but it'll break on a gocb bump.

Verified across the whole PR: gofmt -l clean on all 119 files, golangci-lint --new-from-rev clean in the main module, 40/40 CI checks SUCCESS, and every -race/-count=5 failure reproduces identically on development (the websocket.go:47 race, the port-2121 isPortAvailable probe, the 11 redis TestPubSub_* races, cmd/terminal TestSpinner, the remotelogger flake). Nothing introduced.

- run_test: drop the "hook error aborts startup" row. gofr-dev#3942 changes that
  path to exit the process, so the test would take the binary down once
  both land; gofr-dev#3942 owns that behavior. The canceled-hook case stays.
- container: TestExpectSelect_MismatchCases pinned sql_mock.go's
  response-before-validation order and its swapped/%d format arguments.
  Keep only the argument-count mismatch, asserted with exact arguments.
- Remove tests that only padded coverage: metrics/exporters exporter_test.go
  and the TestCustomError_Error addition to testutil/error_test.go (both
  target files were already fully covered by other tests).
- pubsub/google: alias the grpc status import as grpcstatus so the local
  status variables no longer shadow it (gocritic importShadow).
- pubsub/kafka: share one expected-broker helper between the two health
  tests so the repeated literals no longer trip goconst on health.go.
- gcs: TestStartRetryConnect_ExitsImmediately now fails if startRetryConnect
  has not returned within 2s. Without its early return it previously still
  passed, just 60s slower.
- eventhub: TestMessage_Commit had two rows running the same line; keep one.
- nats: replace three always-true Contains(stdout, "") checks with an
  assertion that output appears only when expected.
- Stop pinning defects: drop surrealdb's incomplete-credentials Connect case
  (it certified a silent unauthenticated "connected" state), opentsdb's
  invalid-datapoints row (wrong sentinel), and dgraph's exact "<nil>"
  health-check log text.
- influxdb: the wrapper fakes returned the same value from every method, so
  calling the wrong upstream passed. Per-method recorders now assert the
  exact upstream method, its arguments, and the passed-through value/error.
- Remove weak tests: the kv-store/nats MockKeyValueEntry test (a test double
  asserting its own literals), the cassandra/scylladb iterator tests (a
  closed session's Iter is always zero-valued, so they could not tell a
  real wrapper from constants), and opentsdb's nine span-name-only cases
  (collapsed to the nil-tracer case).
@NitinKumar004

Copy link
Copy Markdown
Contributor Author

Thanks for going through all of it, and for the mutation runs. That's much more than I expected on a PR this size.

Pushed three commits on top: bf2b864 (main module), f6def5f (pubsub + file) and d514258 (the other datasources). They're grouped along the same module boundaries you suggested.

1. run_test.go vs #3942. App has no exit field on development yet, so app.exit = func(int) {} wouldn't compile on this branch. I dropped the "hook error aborts startup" row here instead. #3942 is changing that path to exit the process and brings its own tests for it, so you don't need to carry anything. The canceled-hook case stays, since that path still returns under #3942.

2. Tests pinning defects. Removed all three, plus the dgraph one from your list:

  • the surrealdb incomplete-credentials Connect case
  • the opentsdb invalid datapoints type row
  • the sql_mock order/format assertions (kept only the arg-count mismatch, with exact arguments)
  • the exact <nil> log text in dgraph's health-check test

I re-ran your examples: adding the error log plus c.db = nil in surrealdb, switching :204 to errInvalidParam, and changing the dgraph log text. The suite stays green for all three now.

3. influxdb. Replaced the fakes with per-method recorders. Each method records its name and args and returns a value (and error) unique to that method. Every row asserts the exact upstream call, its args, and the pass-through. Both of your mutations (FindBucketByID → FindBucketByName, CreateOrganizationWithName → FindOrganizationByName) fail now.

4. Lint.

  • google: the status import is aliased as grpcstatus.
  • kafka: the two health tests share one expected-broker helper, so the repeated status/isController literals are gone.
  • With golangci-lint 2.12.2, google and kafka now show exactly the same issue set as development.
  • I left the 16 submodule goconst findings on untouched lines out of this PR. The existing goconst test exclusion in .golangci.yml doesn't stop test literals from counting toward production findings. Happy to propose a config change separately.

5. Weak tests. Everything from the table is done:

  • gcs: the immediacy test is time-bounded now. Deleting the early return fails it at 2s instead of passing at 61s.
  • eventhub: TestMessage_Commit is down to one case.
  • Removed: exporter_test.go, the added case in testutil/error_test.go (reverted to base), and the MockKeyValueEntry test.
  • opentsdb: observability is collapsed to the nil-tracer case. That module has no otel SDK, so span names couldn't be asserted without a go.mod change.
  • cassandra/scylladb: the iterator tests are removed. You were right: a closed session's Iter is always zero-valued, so nothing could tell the wrapper apart from constants.
  • nats: the always-true Contains(stdout, "") is replaced with an "output only when expected" check. The same pattern turned up in two more places in that file, so I fixed those too.

6. Coverage basis. Thanks for re-measuring. Under CI's method (mocks excluded) I get 90.67% → 95.65%, essentially your figure. The description now leads with that. It says the per-file table uses the narrower per-package basis, gives your container/common.go examples, drops the two 100%→100% rows, and lists responder.go and traces/exporters/config.go as intentionally uncovered, since they're no-op declarations with no statements.

7. Footer. Removed.

8. Size. I'd like to keep it as one PR if that's OK. It's test-only, and the commits are grouped by area (12 plus these 3 review commits), so it can be read commit by commit. If you'd still prefer a split once you've looked at this round, I'll do it along the module boundaries you listed.

Local check before pushing: CI-style runs pass for the main module and all touched submodules, go mod tidy -diff is clean, and lint (--new-from-rev) reports 0. One pkg/gofr/websocket test (TestWSUpgrader_RealConnectionConflicts_Failure) failed once in a full parallel run. This PR doesn't touch that package, and it passes 10/10 on its own here and 20/20 on development, so it looks like a timing flake.

For the things you found that aren't this PR's job, I'll open separate issues unless you'd rather track them differently:

  • surrealdb Connect reporting connected after invalid credentials
  • opentsdb's wrong sentinel at :204
  • the swapped sql_mock format arguments
  • the CommonFileSystem retry-state data race
  • nats stream_manager matching on the error string instead of errors.Is
  • initTest/setupDB calling ctrl.Finish() early (cassandra, scylladb, dgraph)
  • dgraph logging <nil> on the empty-response path
  • couchbase tests asserting a gocb error string that has no sentinel
  • the arangodb generated mocks inflating its coverage denominator

@aryanmehrotra aryanmehrotra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All eight addressed. I checked the delta rather than taking the summary on trust, and the shape of it is the thing I most want to call out: 17 files, every one a _test.go, net −78 lines (332 added, 410 removed). You deleted padding and rewrote the weak fake instead of adding more tests on top. That is the right direction and it is rarer than it should be.

Item by item:

  • The os.Exit landmine is gone. The "hook error aborts startup" subtest is removed; only TestApp_Run_StartupHookCanceled remains, which exercises the path that returns rather than exits. That was the one blocking item — it would have taken the whole pkg/gofr test binary down with no --- FAIL marker once #3942 lands, killing every test after it. Nothing left for either of us to carry.
  • surrealdb's incomplete credentials case is gone, so Connect()'s silent-failure branch is no longer certified as correct behavior. Please do file that one separately — a client that reports itself connected with unauthenticated credentials and emits zero log lines is worth its own fix.
  • opentsdb's wrong-sentinel case is gone.
  • The influxdb wrappers are properly rebuilt. calls []upstreamCall with a record(method, args...) helper — per-method recording, which is exactly the dgraph/interfaces_test.go pattern I pointed at. Those 154 lines can now actually catch a wrapper calling the wrong upstream, which was the whole problem.
  • importShadow fixed via the grpcstatus alias.
  • exporter_test.go deleted.
  • The coverage-baseline footnote is in, so the 84.9% figure can't be mistaken for CI's -coverpkg number.
  • Attribution footer gone.

One correction from me: I asked you to drop testutil/error_test.go as padding. That was wrong — this PR doesn't touch that file at all; it pre-exists on development. My reviewer mis-attributed it and I passed it on without checking the diff. Sorry for the noise.

I also confirmed nothing else came along for the ride: no go.mod, go.sum, go.work, .github/ or .golangci.yml changes, and zero non-test files across the whole PR.

For the record on what the review established before the fixes, because it should be visible on the PR: across three separate passes covering all 119 files — roughly 17,400 lines read and 43 mutations planted in production code — ~93% of the added lines were already functional, and 39 of 43 mutations were caught. The concern with a large coverage PR is tests written to move a number, and that is not what this is. The fakes are real protocol fakes: an httptest CBOR RPC server driving the actual SurrealDB client (22.2% → 92.3%), a real gRPC api.DgraphServer, a hand-rolled NATS TCP handshake, an embedded BadgerDB, miniredis, pstest.NewServer, sftp.NewRequestServer over net.Pipe() with no sockets at all. Zero t.Skip, zero os.Getenv, zero network dependencies — the whole thing runs with no Docker.

And Test_APIKeyAuthMiddleware, which was literally t.Logf("Test_APIKeyAuthMiddleware"), is now a real table asserting status and the context value. Deleting a vacuous test is the opposite of farming coverage.

Two things I'd still encourage, neither blocking:

Splitting. 119 files is more than one review pass can honestly cover — the landmine above was invisible to CI, invisible to a diff skim, and only surfaced because one pass merged the branch and ran the suite. If you're open to it, the seam is the Go module boundary, which is already how CI shards this: main module (47 files), pubsub + file (36), the remaining 17 datasource submodules (36). Split for reviewability, not to prune — the functional ratio doesn't justify cutting anything.

Follow-up issues worth filing from what this turned up, none of them yours to fix here:

  • file/common_fs.go:630-648 — connected and disableRetry are unsynchronized against the background retry goroutine. This is a real production data race and it's why file/ftp and file/gcs fail under -race on development today.
  • initTest / setupDB call ctrl.Finish() inside themselves in cassandra, scylladb and dgraph, so it runs before any expectation is registered and gomock never reports a missing call. Every .Times(1) in those packages guards only against a wrong call, never against no call.
  • nats/stream_manager.go:56 matches an error string rather than errors.Is(err, jetstream.ErrStreamNameAlreadyInUse). It works against the pinned nats.go@v1.53.1, but a bump that rewords the description turns a tolerated condition into a hard failure.
  • arangodb's coverage will keep reading artificially low until its ~6,000 lines of generated gomock move behind a build tag or into _test.go — 517 of its 592 profiled functions are mocks at 0%.

Thanks for turning this around quickly and for taking the substantive asks rather than arguing the easy ones.

@aryanmehrotra

Copy link
Copy Markdown
Member

Following up after actually running this at d51425809, rather than approving on a diff read. Approval stands — but two things came out of it that you should know, and both land on me rather than you.

What I ran: 27 Go modules × 5 modes (build, vet, -count=1, -race, -count=5) = 135 runs, 1,890s of test time, submodules from inside each directory with GOWORK=off to match CI's Submodule Unit Testing. Plus a full development baseline for every failure, coverage on both trees, golangci-lint --new-from-rev per module, and the merged tree against #3942.

Result: zero failures attributable to this PR. Every failure reproduces identically on development with the same command:

failure label
file/ftp, file/gcs under -race pre-existing — CommonFileSystem.disableRetry (common_fs.go:637/642) is an unsynchronised bool read from the retry goroutine, in production code
pkg/gofr websocket races, cmd/terminal TestSpinner, remotelogger pre-existing, on the known list
datasource/redis 17 TestPubSub_* under -race pre-existing — and development is strictly worse, failing two additional tests this tree doesn't
pubsub/mqtt TestMQTT_Query_SuccessCases pre-existing — the test is byte-identical on development, which also fails TestMQTT_SubscribeSuccess on top
pkg/gofr and pkg/gofr/metrics under -count=5 pre-existing — port-2121 probe and the global Prometheus registry
examples/* environmental — no MySQL/Redis/Kafka locally; this PR touches zero example files

24 of 26 submodules are a clean sweep on all five modes. Every pkg/... package passes -count=1, which is the CI-equivalent signal. gofmt -l over all 117 changed files: empty. golangci-lint --new-from-rev on the root module and all 26 submodules: 0 issues each. go build + go vet in all 31 modules: clean, so no deleted helper broke an untouched package. go mod tidy -diff: clean.

Coverage: not a single package went down. 45 packages measured, all up — surrealdb +69.9, cloudsql +44.1, solr +32.5, eventhub +26.1, pkg/gofr +6.2. Worth noting two of those: metrics/exporters went up 3.0pp even though exporter_test.go was deleted, and pkg/gofr went up 6.2pp even though TestApp_Run_StartupHookFailure was deleted. The trimming cost nothing.

The influxdb rewrite works. Both mutations that survived the old fakes are now killed, in both the fail=false and fail=true arms:

internal.go:60  FindBucketByID → FindBucketByName
  --- FAIL: Test_BucketAPIWrapper/FindBucketByID/fail=false
      expected: {method:"FindBucketByID",   args:[ctx, "bucket-id"]}
      actual  : {method:"FindBucketByName", args:[ctx, "bucket-id"]}

internal.go:28  CreateOrganizationWithName → FindOrganizationByName
  --- FAIL: Test_OrganizationAPIWrapper/CreateOrganizationWithName/fail=false

runWrapperCases asserting exact method-name-plus-args equality is what does it. That is the right shape and it is now genuinely load-bearing.

Nothing was orphaned by the deletions — unused linter reports 0 in every package that lost a test, and the helpers the deleted tests used (closedCassandraSession, closedGocqlSession, errHookFailed, errInvalidResponseType) all still have live callers.


Two items, both mine to carry:

1. There is a second collision with #3942, and it is my PR's fault, not yours

Your new TestApp_startMCPServer fails deterministically (3/3) on the merged tree:

--- FAIL: TestApp_startMCPServer/MCP_server_shut_down_before_it_started
    missing call(s) to MockLogger.Logf("Starting MCP server on port: %d", 50628)

The merge itself is clean — no conflicts, and the suite completes with a normal FAIL gofr.dev/pkg/gofr 36.475s line rather than dying silently. The landmine you fixed by deleting the hook subtest is genuinely gone; that fix worked. This is a different, semantic one.

Cause: on development, mcpServer.Run logs "Starting MCP server on port: %d" as its first statement (mcp.go:92), before the stopped check — so your stopped case correctly expects both lines. #3942 moves that log below the guards (mcp.go:218), because logging "starting" immediately before refusing to serve is wrong. So your assertion is right against development and wrong against my branch.

I'm taking it. Your test asserts today's behaviour correctly; my PR is the one changing the behaviour, so my PR carries the test update. Nothing for you to change, and please don't pre-adapt to an unmerged branch of mine.

2. Deleting that subtest left run.go:91 uncovered — also mine

grep -rn 'Startup failed' pkg/ returns exactly one hit, run.go:91, and zero test hits. The "hook error aborts startup" subtest was the only thing reaching that branch, and it was deleted because of the os.Exit problem I raised — which #3942 introduces. Package coverage still rose 6.2pp, so nothing regressed on net, but that error path now has nothing on it.

Restoring it safely needs app.exit set, which is exactly what #3942 adds. So that belongs in my PR too.


Two smaller things, neither needing action here:

  • The opentsdb "invalid datapoints type" and surrealdb "incomplete credentials" cases were deleted rather than corrected. Both packages still gained a lot (+16.1 and +69.9), so nothing regressed — but the opentsdb one was reaching a real branch (opentsdb.go:204, the Must be []DataPoint path) and only its expected sentinel was wrong. A corrected case asserting the right error would have been better than removing it. Not worth another round trip; noting it so the branch isn't forgotten.
  • TestStaticHandlerGetwdError (gofr_test.go:1348) skips on macOS — Darwin's kernel still resolves an unlinked cwd so os.Getwd() doesn't error. The skip is correct and well-commented, and CI is Linux so the branch is exercised there. Just means that one new assertion is untested on a Mac.

Thanks for turning the fixes around as fast as you did — the shape of them (delete padding, rewrite the weak fake, net −78 lines) was the right answer.

@NitinKumar004

Copy link
Copy Markdown
Contributor Author

Thanks for merging, and for the full run across all the modules. No worries about testutil/error_test.go.

Filed the follow-ups from this review:

NitinKumar004 added a commit to NitinKumar004/gofr that referenced this pull request Sep 24, 2026
Resolve the run_test.go conflict with gofr-dev#4274: keep development's new tests
and add this PR's runCMD exit-code and cleanup-order tests and the onClose
hook on closeRecordingLogger.
NitinKumar004 added a commit to NitinKumar004/gofr that referenced this pull request Sep 24, 2026
Resolve conflicts with gofr-dev#4274 in the elasticsearch and opentsdb tests by
keeping both sides' tests, and move development's new opentsdb health-check
test from the removed dialTimeout seam to dialContext.
aryanmehrotra added a commit that referenced this pull request Sep 24, 2026
…tests compiling

Rebased onto development, which brought in two untagged tests that do not
hold under a tag, plus akshat-kumar-singhal's review of the CI step.

**The "Default build links the same packages as before" step is removed.**
It diffed `go list -deps ./...` against the merge base across the whole
root module, and this job runs on every pull request -- so it meant "no PR
may add a package or a dependency, ever". That is not a rule this repo has,
and it would have gone red on changes with nothing to do with build tags.

What it was protecting is already covered, and better, by the step above
it: `grep -Eq "$pattern" /tmp/deps.default` fails when a tagged-out package
is missing from the DEFAULT build, so the per-tag checks cannot pass
vacuously and an untagged build cannot quietly stop linking them. A comment
says so, and asks that the broad diff not be re-added.

**Two tests from development needed a home under the tags.**
TestApp_setupGraphQL_MissingSchema (#4274) names errSchemaMissing, which
graphql.go compiles out; it moves from the untagged gofr_test.go into
graphql_test.go, which already carries !gofr_nographql.
TestContainer_createKafkaPubSub_InvalidConfigs asserts what kafka.New's own
validation logs, and under gofr_nopubsub there is no kafka.New -- it takes
the pubsubBackendsLinked skip the file already uses for three other tests.

Both failed `go vet`/`go test` under their tag while the non-test build
stayed green, which is the tagged-build blind spot this PR's CI job exists
for: it caught them.

**The doc now states where its counts were measured** -- darwin/arm64,
Go 1.26.3 -- and that they are platform-dependent, so a linux/amd64 reader
seeing roughly twenty fewer knows why the absolute numbers differ and the
deltas do not.

Verified across all five tag configurations: build, vet and test clean.
typos clean, gofmt clean, go.yml parses. golangci-lint reports 5 issues
against 7 on development, none new.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants