Skip to content

MySQL/PgSQL Authentication unit tests (Phase 2.2)#5485

Merged
renecannao merged 3 commits intov3.0-5473from
v3.0-5474
Mar 22, 2026
Merged

MySQL/PgSQL Authentication unit tests (Phase 2.2)#5485
renecannao merged 3 commits intov3.0-5473from
v3.0-5474

Conversation

@renecannao
Copy link
Contributor

@renecannao renecannao commented Mar 21, 2026

Summary

Implements Phase 2.2 (#5474) of the Unit Testing Framework: comprehensive unit tests for MySQL_Authentication and PgSQL_Authentication.

  • 58 TAP test cases across 13 test functions, covering both MySQL and PgSQL authentication subsystems
  • Runs in <0.01 seconds with zero infrastructure dependencies
  • Validates the Phase 2.1 harness works correctly for real component testing

Test Coverage

MySQL_Authentication (43 tests)

Area Tests What's Verified
Core CRUD 5 add/exists/lookup cycle with field validation
exists() scope 2 Only checks frontends, not backends
Duplicate handling 3 add() overwrites password, hostgroup, max_connections
Deletion 4 del() removes user; returns false for already-deleted
Lookup miss 2 Returns empty struct for nonexistent users
SHA1 credentials 4 set_SHA1() store/retrieve, nonexistent user handling
Clear-text passwords 3 PRIMARY and ADDITIONAL password slots
Connection limits 5 increase/decrease with max_connections enforcement
Inactive pattern 2 set_all_inactive + remove_inactives config reload
Reset 2 Clears both frontend and backend users
Checksums 4 Zero when empty, changes on add/modify
Memory tracking 3 Increases on add, baseline after reset
FE/BE separation 4 Same username with different credentials per type

PgSQL_Authentication (15 tests)

Area Tests What's Verified
Core CRUD 5 PgSQL-specific lookup() signature (returns char*)
Del + Reset 4 Deletion and bulk reset
Connection limits 4 No PASSWORD_TYPE param (PgSQL difference)
Inactive pattern 2 Config reload simulation

Depends On

Test plan

  • All 58 tests pass on macOS (arm64)
  • Verify on Linux (CI)
  • Run with ASAN to verify no memory leaks

Summary by CodeRabbit

  • Tests
    • Added comprehensive authentication unit tests covering add/lookup/delete, password types, overwrite behavior, connection limits/enforcement, bulk inactive removal, reset/cleanup, runtime checksum and memory usage checks.
    • Added a unit-test build target so the new authentication tests are included in the test suite.

Copilot AI review requested due to automatic review settings March 21, 2026 19:57
@coderabbitai
Copy link

coderabbitai bot commented Mar 21, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4ebb0635-5336-4f38-9391-fd1cec6b4827

📥 Commits

Reviewing files that changed from the base of the PR and between 8b6c15d and 051435f.

📒 Files selected for processing (2)
  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/auth_unit-t.cpp

📝 Walkthrough

Walkthrough

A new unit test suite for authentication was added: auth_unit-t.cpp implements extensive MySQL and PostgreSQL authentication tests (CRUD, credentials, connection counting, inactive cleanup, checksum, memory), and test/tap/tests/unit/Makefile was updated to build the auth_unit-t test binary.

Changes

Cohort / File(s) Summary
Build Configuration
test/tap/tests/unit/Makefile
Added auth_unit-t to UNIT_TESTS and provided build/link rule that compiles auth_unit-t.cpp and links test helpers, core library archive, static libs, and platform flags.
Authentication Unit Tests
test/tap/tests/unit/auth_unit-t.cpp
New comprehensive unit test file covering MySQL_Authentication and PgSQL_Authentication: add/exists/lookup/del flows, SHA1 and clear-text password handling, frontend/backend separation, connection limits (increase/decrease), inactive-user bulk removal, reset behavior, runtime checksum and memory usage checks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐰 I hopped through code with whiskers twitching bright,

Tests for auth now ready, day and night,
MySQL and Pg dance, hashes checked with care,
Connections counted, inactive users beware,
A tiny rabbit cheers — tests pass, take flight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'MySQL/PgSQL Authentication unit tests (Phase 2.2)' accurately summarizes the main change: adding comprehensive unit tests for authentication modules across both MySQL and PostgreSQL implementations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v3.0-5474

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request implements Phase 2.2 of the Unit Testing Framework by adding comprehensive unit tests for MySQL_Authentication and PgSQL_Authentication. It includes 58 new test cases that cover various aspects of the authentication subsystems, ensuring thorough validation of their functionality and performance. The changes also validate that the Phase 2.1 harness works correctly for real component testing.

Highlights

  • Comprehensive Unit Tests: This PR introduces 58 TAP test cases for MySQL_Authentication and PgSQL_Authentication, enhancing the unit testing framework.
  • Performance and Dependencies: The tests run in under 0.01 seconds and have zero infrastructure dependencies, ensuring fast and isolated testing.
  • Coverage: The tests cover core CRUD operations, credential management, connection counting, bulk operations, runtime checksums, memory tracking, and frontend/backend separation for both MySQL and PgSQL authentication subsystems.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds a comprehensive suite of unit tests for the MySQL and PostgreSQL authentication subsystems. The tests are well-structured, cover a wide range of scenarios as detailed in the description, and significantly improve the test coverage for these critical components. My review focuses on a few areas for improvement: using C++-style casts for better type safety, clarifying a test failure message, and fixing a potential memory leak in one of the test cases. Overall, this is a valuable contribution to the project's testing infrastructure.

"PgSQL: lookup() returns correct max_connections");

if (password) free(password);
if (attrs) free(attrs);

Choose a reason for hiding this comment

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

high

There is a potential memory leak here. The lookup function for PgSQL_Authentication can allocate memory for sha1_pass if it exists for the user. The pointer sha1 is passed to lookup, but it's never freed. While in this specific test case sha1_pass is not set for the user pg_alice, so no memory is allocated, this is fragile. Future changes or other tests could introduce a leak.

You should add a check to free sha1 if it was allocated.

	if (attrs) free(attrs);
	if (sha1) free(sha1);

Comment on lines +37 to +53
static bool mysql_add_frontend(MySQL_Authentication *auth,
const char *user, const char *pass,
int default_hg = 0, int max_conn = 100)
{
return auth->add(
(char *)user, (char *)pass, USERNAME_FRONTEND,
false, // use_ssl
default_hg, // default_hostgroup
(char *)"", // default_schema
false, // schema_locked
false, // transaction_persistent
false, // fast_forward
max_conn, // max_connections
(char *)"", // attributes
(char *)"" // comment
);
}

Choose a reason for hiding this comment

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

medium

Throughout this file, C-style casts like (char *) are used to cast away const from string literals and const char* parameters. This is risky and not idiomatic C++. It's better to use const_cast<char*>(). This makes the intent explicit and is type-safer than a C-style cast.

While the underlying API in MySQL_Authentication is not const-correct, using const_cast in the test code is a good practice.

This applies to other helper functions and direct API calls in this file as well.

static bool mysql_add_frontend(MySQL_Authentication *auth,
	const char *user, const char *pass,
	int default_hg = 0, int max_conn = 100)
{
	return auth->add(
		const_cast<char *>(user), const_cast<char *>(pass), USERNAME_FRONTEND,
		false,            // use_ssl
		default_hg,       // default_hostgroup
		const_cast<char *>(""),       // default_schema
		false,            // schema_locked
		false,            // transaction_persistent
		false,            // fast_forward
		max_conn,         // max_connections
		const_cast<char *>(""),       // attributes
		const_cast<char *>("")        // comment
	);
}

ok(memcmp(ad.sha1_pass, sha1_hash, SHA_DIGEST_LENGTH) == 0,
"MySQL: SHA1 hash matches what was set");
} else {
ok(0, "MySQL: SHA1 hash matches (skipped - null)");

Choose a reason for hiding this comment

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

medium

The message MySQL: SHA1 hash matches (skipped - null) for a failing test (ok(0, ...) is a failure) is confusing. The message suggests the test was skipped, but it's actually reported as a failure. This can make debugging test failures more difficult.

A clearer message would state that the test failed because the hash was unexpectedly null.

		ok(0, "MySQL: SHA1 hash was unexpectedly null");

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new TAP-based unit test binary to exercise the MySQL_Authentication and PgSQL_Authentication components in isolation using the Phase 2.1 unit-test harness.

Changes:

  • Introduces auth_unit-t.cpp with 58 TAP assertions spanning MySQL and PgSQL authentication CRUD, connection counting, bulk inactive patterns, checksums, and memory usage.
  • Extends the unit-test Makefile to build the new auth_unit-t target alongside the existing smoke test.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
test/tap/tests/unit/auth_unit-t.cpp New unit test suite covering MySQL/PgSQL authentication behaviors via the harness globals.
test/tap/tests/unit/Makefile Adds auth_unit-t to UNIT_TESTS and provides a build rule for it.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +236 to +256
static void test_mysql_clear_text_password() {
mysql_add_frontend(GloMyAuth, "ctpuser", "original");

bool set_ok = GloMyAuth->set_clear_text_password(
(char *)"ctpuser", USERNAME_FRONTEND,
"clearpass", PASSWORD_TYPE::PRIMARY);
ok(set_ok == true,
"MySQL: set_clear_text_password(PRIMARY) returns true");

set_ok = GloMyAuth->set_clear_text_password(
(char *)"ctpuser", USERNAME_FRONTEND,
"altpass", PASSWORD_TYPE::ADDITIONAL);
ok(set_ok == true,
"MySQL: set_clear_text_password(ADDITIONAL) returns true");

bool set_fail = GloMyAuth->set_clear_text_password(
(char *)"nobody", USERNAME_FRONTEND,
"pass", PASSWORD_TYPE::PRIMARY);
ok(set_fail == false,
"MySQL: set_clear_text_password() returns false for nonexistent user");
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

test_mysql_clear_text_password() only asserts that set_clear_text_password() returns true/false; it never verifies the stored PRIMARY/ADDITIONAL clear-text passwords are actually retrievable. To make this test meaningful (and match the stated intent), perform a lookup() after each set and assert ad.clear_text_password[PRIMARY] / [ADDITIONAL] match the expected values (and free the returned account_details_t).

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +155
ok(strcmp(ad.password, "new_pass") == 0,
"MySQL: add() overwrites password on duplicate");
ok(ad.default_hostgroup == 2,
"MySQL: add() overwrites default_hostgroup on duplicate");
ok(ad.max_connections == 20,
"MySQL: add() overwrites max_connections on duplicate");
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

This test calls strcmp(ad.password, ...) without checking ad.password is non-null. If add() or lookup() ever fails unexpectedly, the test will segfault and abort the whole suite instead of reporting a TAP failure. Prefer the pattern used elsewhere in this file (ad.password != nullptr && strcmp(...) == 0) so failures remain diagnosable.

Copilot uses AI. Check for mistakes.
Comment on lines +423 to +430
ok(strcmp(fe.password, "frontend_pass") == 0,
"MySQL: frontend lookup returns frontend password");
ok(fe.default_hostgroup == 1,
"MySQL: frontend lookup returns frontend hostgroup");

account_details_t be = GloMyAuth->lookup(
(char *)"dualuser", USERNAME_BACKEND, dup);
ok(strcmp(be.password, "backend_pass") == 0,
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The frontend/backend separation test dereferences fe.password/be.password via strcmp() without a null check. If a lookup fails, this will crash the test binary rather than producing a TAP assertion failure. Add a null guard (as done in other tests here) before calling strcmp() so errors are reported cleanly.

Suggested change
ok(strcmp(fe.password, "frontend_pass") == 0,
"MySQL: frontend lookup returns frontend password");
ok(fe.default_hostgroup == 1,
"MySQL: frontend lookup returns frontend hostgroup");
account_details_t be = GloMyAuth->lookup(
(char *)"dualuser", USERNAME_BACKEND, dup);
ok(strcmp(be.password, "backend_pass") == 0,
ok(fe.password && strcmp(fe.password, "frontend_pass") == 0,
"MySQL: frontend lookup returns frontend password");
ok(fe.default_hostgroup == 1,
"MySQL: frontend lookup returns frontend hostgroup");
account_details_t be = GloMyAuth->lookup(
(char *)"dualuser", USERNAME_BACKEND, dup);
ok(be.password && strcmp(be.password, "backend_pass") == 0,

Copilot uses AI. Check for mistakes.
@renecannao
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

renecannao added a commit that referenced this pull request Mar 21, 2026
- Add null guards before strcmp() in test_mysql_add_overwrites and
  test_mysql_frontend_backend_separation to prevent segfaults on
  unexpected lookup failures

- Fix misleading "skipped - null" message in SHA1 test fallback to
  clearly indicate an unexpected null

- Free sha1 pointer in PgSQL lookup test to prevent potential memory
  leak if sha1_pass is ever set

- Enhance test_mysql_clear_text_password to verify stored PRIMARY and
  ADDITIONAL passwords are actually retrievable via lookup(), not just
  that set_clear_text_password() returns true (2 new test cases, plan
  updated from 58 to 60)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
test/tap/tests/unit/auth_unit-t.cpp (2)

183-192: Minor: Consider calling free_account_details(ad) for consistency.

While the struct fields are null for a nonexistent user and the test process is short-lived (per ProxySQL test conventions), adding the cleanup call would maintain consistency with other test functions.

♻️ Optional fix for consistency
 	ok(ad.password == nullptr,
 		"MySQL: lookup() returns null password for nonexistent user");
 	ok(ad.username == nullptr,
 		"MySQL: lookup() returns null username for nonexistent user");
+
+	free_account_details(ad);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/unit/auth_unit-t.cpp` around lines 183 - 192, In
test_mysql_lookup_not_found, after calling GloMyAuth->lookup(...) and asserting
ad.password and ad.username are null, call free_account_details(ad) to free any
allocated resources and match cleanup used in other tests; this uses the
existing free_account_details function to release the account_details_t returned
by GloMyAuth->lookup.

217-222: Good defensive check, but consider using skip() for TAP compliance.

The conditional avoids a crash if sha1_pass is unexpectedly null, which is good. However, ok(0, "...") reports a failure rather than a skip. If you want proper TAP semantics for conditional tests, you could use the skip() TAP directive.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/unit/auth_unit-t.cpp` around lines 217 - 222, The conditional
currently uses ok(0, "...") which records a failure when ad.sha1_pass is null;
change that branch to call the TAP skip directive instead (e.g., skip("MySQL:
SHA1 hash matches (skipped - null)")) so the test is properly skipped; keep the
positive branch as ok(memcmp(ad.sha1_pass, sha1_hash, SHA_DIGEST_LENGTH) == 0,
"MySQL: SHA1 hash matches what was set") and only call skip() when ad.sha1_pass
== nullptr.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/tap/tests/unit/auth_unit-t.cpp`:
- Around line 183-192: In test_mysql_lookup_not_found, after calling
GloMyAuth->lookup(...) and asserting ad.password and ad.username are null, call
free_account_details(ad) to free any allocated resources and match cleanup used
in other tests; this uses the existing free_account_details function to release
the account_details_t returned by GloMyAuth->lookup.
- Around line 217-222: The conditional currently uses ok(0, "...") which records
a failure when ad.sha1_pass is null; change that branch to call the TAP skip
directive instead (e.g., skip("MySQL: SHA1 hash matches (skipped - null)")) so
the test is properly skipped; keep the positive branch as
ok(memcmp(ad.sha1_pass, sha1_hash, SHA_DIGEST_LENGTH) == 0, "MySQL: SHA1 hash
matches what was set") and only call skip() when ad.sha1_pass == nullptr.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2cdf40fd-b096-421e-8af0-07db0cbd8122

📥 Commits

Reviewing files that changed from the base of the PR and between a538532 and 8b6c15d.

📒 Files selected for processing (2)
  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/auth_unit-t.cpp

Comprehensive unit tests for MySQL_Authentication and PgSQL_Authentication
covering 58 test cases across 13 test functions. Runs in <0.01s with no
infrastructure dependencies.

MySQL_Authentication coverage:
- Core CRUD: add/exists/lookup/del with frontend and backend users
- Duplicate handling: add() overwrites password, hostgroup, max_connections
- exists() only checks frontends (not backends) — documented quirk
- lookup() returns empty struct for nonexistent users
- SHA1 credential storage and retrieval via set_SHA1()
- Clear-text password management (PRIMARY and ADDITIONAL slots)
- Connection counting: increase/decrease with max_connections enforcement
- Bulk operations: set_all_inactive + remove_inactives config reload pattern
- reset() clears all frontend and backend users
- Runtime checksum: zero when empty, changes on add/modify
- Memory tracking: increases on add, returns to baseline on reset
- Frontend/backend separation: same username, different credentials

PgSQL_Authentication coverage:
- Core CRUD with PgSQL-specific lookup() signature (returns char*)
- del() and reset() behavior
- Connection counting (no PASSWORD_TYPE parameter)
- Inactive pattern (set_all_inactive + remove_inactives)
Registers the Authentication unit test binary in the build system so
it is built alongside smoke_test-t via 'make build_tap_tests'.
- Add null guards before strcmp() in test_mysql_add_overwrites and
  test_mysql_frontend_backend_separation to prevent segfaults on
  unexpected lookup failures

- Fix misleading "skipped - null" message in SHA1 test fallback to
  clearly indicate an unexpected null

- Free sha1 pointer in PgSQL lookup test to prevent potential memory
  leak if sha1_pass is ever set

- Enhance test_mysql_clear_text_password to verify stored PRIMARY and
  ADDITIONAL passwords are actually retrievable via lookup(), not just
  that set_clear_text_password() returns true (2 new test cases, plan
  updated from 58 to 60)
@renecannao renecannao merged commit f3b9eda into v3.0-5473 Mar 22, 2026
3 of 4 checks passed
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
C Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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