-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Adding new ExternalAuthProviderError that will be raised when we receive 'problem with LDAP service' response from server. #3808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
petyaslavova
merged 2 commits into
master
from
ps_add_new_external_auth_provider_error_type
Oct 21, 2025
+190
−0
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| import socket | ||
| from unittest.mock import patch | ||
|
|
||
| import pytest | ||
| from redis.client import Redis | ||
| from redis.exceptions import ExternalAuthProviderError | ||
| from tests.conftest import skip_if_hiredis_parser | ||
|
|
||
|
|
||
| class MockSocket: | ||
| """Mock socket that simulates Redis protocol responses.""" | ||
|
|
||
| def __init__(self): | ||
| self.sent_data = [] | ||
| self.closed = False | ||
| self.pending_responses = [] | ||
|
|
||
| def connect(self, address): | ||
| pass | ||
|
|
||
| def send(self, data): | ||
| """Simulate sending data to Redis.""" | ||
| if self.closed: | ||
| raise ConnectionError("Socket is closed") | ||
| self.sent_data.append(data) | ||
|
|
||
| # Analyze the command and prepare appropriate response | ||
| if b"HELLO" in data: | ||
| response = b"%7\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n7.4.0\r\n$5\r\nproto\r\n:3\r\n$2\r\nid\r\n:1\r\n$4\r\nmode\r\n$10\r\nstandalone\r\n$4\r\nrole\r\n$6\r\nmaster\r\n$7\r\nmodules\r\n*0\r\n" | ||
| self.pending_responses.append(response) | ||
| elif b"SET" in data: | ||
| response = b"+OK\r\n" | ||
| self.pending_responses.append(response) | ||
| elif b"GET" in data: | ||
| # Extract key and provide appropriate response | ||
| if b"hello" in data: | ||
| response = b"$5\r\nworld\r\n" | ||
| self.pending_responses.append(response) | ||
| # Handle specific keys used in tests | ||
| elif b"ldap_error" in data: | ||
| self.pending_responses.append(b"-ERR problem with LDAP service\r\n") | ||
| else: | ||
| self.pending_responses.append(b"$-1\r\n") # NULL response | ||
| else: | ||
| self.pending_responses.append(b"+OK\r\n") # Default response | ||
|
|
||
| return len(data) | ||
|
|
||
| def sendall(self, data): | ||
| """Simulate sending all data to Redis.""" | ||
| return self.send(data) | ||
|
|
||
| def recv(self, bufsize): | ||
| """Simulate receiving data from Redis.""" | ||
| if self.closed: | ||
| raise ConnectionError("Socket is closed") | ||
|
|
||
| # Use pending responses that were prepared when commands were sent | ||
| if self.pending_responses: | ||
| response = self.pending_responses.pop(0) | ||
| return response[:bufsize] # Respect buffer size | ||
| else: | ||
| # No data available - this should block or raise an exception | ||
| # For can_read checks, we should indicate no data is available | ||
| import errno | ||
|
|
||
| raise BlockingIOError(errno.EAGAIN, "Resource temporarily unavailable") | ||
|
|
||
| def recv_into(self, buffer, nbytes=0): | ||
| """ | ||
| Receive data from Redis and write it into the provided buffer. | ||
| Returns the number of bytes written. | ||
| This method is used by the hiredis parser for efficient data reading. | ||
| """ | ||
| if self.closed: | ||
| raise ConnectionError("Socket is closed") | ||
|
|
||
| # Use pending responses that were prepared when commands were sent | ||
| if self.pending_responses: | ||
| response = self.pending_responses.pop(0) | ||
|
|
||
| # Determine how many bytes to write | ||
| if nbytes == 0: | ||
| nbytes = len(buffer) | ||
|
|
||
| # Write data into the buffer (up to nbytes or response length) | ||
| bytes_to_write = min(len(response), nbytes, len(buffer)) | ||
| buffer[:bytes_to_write] = response[:bytes_to_write] | ||
|
|
||
| return bytes_to_write | ||
| else: | ||
| # No data available - this should block or raise an exception | ||
| # For can_read checks, we should indicate no data is available | ||
| import errno | ||
|
|
||
| raise BlockingIOError(errno.EAGAIN, "Resource temporarily unavailable") | ||
|
|
||
| def fileno(self): | ||
| """Return a fake file descriptor for select/poll operations.""" | ||
| return 1 # Fake file descriptor | ||
|
|
||
| def close(self): | ||
| """Simulate closing the socket.""" | ||
| self.closed = True | ||
| self.address = None | ||
| self.timeout = None | ||
|
|
||
| def settimeout(self, timeout): | ||
| pass | ||
|
|
||
| def setsockopt(self, level, optname, value): | ||
| pass | ||
|
|
||
| def setblocking(self, blocking): | ||
| pass | ||
|
|
||
| def shutdown(self, how): | ||
| pass | ||
|
|
||
|
|
||
| class TestErrorParsing: | ||
| def setup_method(self): | ||
| """Set up test fixtures with mocked sockets.""" | ||
| self.mock_sockets = [] | ||
| self.original_socket = socket.socket | ||
|
|
||
| # Mock socket creation to return our mock sockets | ||
| def mock_socket_factory(*args, **kwargs): | ||
| mock_sock = MockSocket() | ||
| self.mock_sockets.append(mock_sock) | ||
| return mock_sock | ||
|
|
||
| self.socket_patcher = patch("socket.socket", side_effect=mock_socket_factory) | ||
| self.socket_patcher.start() | ||
|
|
||
| # Mock select.select to simulate data availability for reading | ||
| def mock_select(rlist, wlist, xlist, timeout=0): | ||
| # Check if any of the sockets in rlist have data available | ||
| ready_sockets = [] | ||
| for sock in rlist: | ||
| if hasattr(sock, "connected") and sock.connected and not sock.closed: | ||
| # Only return socket as ready if it actually has data to read | ||
| if hasattr(sock, "pending_responses") and sock.pending_responses: | ||
| ready_sockets.append(sock) | ||
| # Don't return socket as ready just because it received commands | ||
| # Only when there are actual responses available | ||
| return (ready_sockets, [], []) | ||
|
|
||
| self.select_patcher = patch("select.select", side_effect=mock_select) | ||
| self.select_patcher.start() | ||
|
|
||
| def teardown_method(self): | ||
| """Clean up test fixtures.""" | ||
| self.socket_patcher.stop() | ||
| self.select_patcher.stop() | ||
|
|
||
| @skip_if_hiredis_parser() | ||
| @pytest.mark.parametrize("protocol_version", [2, 3]) | ||
| def test_external_auth_provider_error(self, protocol_version): | ||
| client = Redis( | ||
| protocol=protocol_version, | ||
| ) | ||
| client.set("hello", "world") | ||
|
|
||
| with pytest.raises(ExternalAuthProviderError): | ||
| client.get("ldap_error") | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Missing
decode_responsesparameter initialization. Consider explicitly setting this to False or True to make the test's expectations clear and avoid relying on default behavior.Copilot uses AI. Check for mistakes.