Commit graph

46 commits

Author SHA1 Message Date
Nick Misasi
8cc1f0b490 feat(13-02): create Python SDK Makefile with dev workflow targets
Add Makefile to python-sdk/ with targets for common development operations:

- venv: Create virtual environment and install dependencies
- proto-gen: Generate Python gRPC code from proto files
- build: Build SDK package (wheel and sdist)
- test: Run pytest test suite
- lint: Run mypy type checking
- clean: Remove build artifacts
- help: Show available targets

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 10:37:24 -05:00
Nick Misasi
a26942bde1 fix(python-sdk): accept dict responses from ExecuteCommand hook
- Add _to_command_response_proto helper to convert dict/wrapper/protobuf
  to CommandResponse protobuf
- Plugin developers can now return plain dicts with response_type, text, etc.
- Also supports CommandResponse wrapper class and raw protobufs
- Export CommandResponse from mattermost_plugin package

This fixes the "ExecuteCommand returned unexpected type: <class 'dict'>"
error when Python plugin handlers return dict responses.
2026-01-20 10:08:47 -05:00
Nick Misasi
c3938bfd77 fix(python-sdk): connect API client before hooks are called
The API client was created but never connected, causing hook handlers
like OnActivate to fail when trying to use self.api methods:
  "Client is not connected. Use 'with' statement or call connect() first."

Fix: Call api_client.connect() in serve_plugin() after creating the
client, before hooks can be invoked.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:20:23 -05:00
Nick Misasi
01643af641 debug: add extensive logging to trace hook registration flow
Go side:
- Log hooks returned by Implemented()
- Log each hook name -> ID mapping
- Log OnActivate implementation status
- Log OnActivate call flow

Python side:
- Log Implemented() return value
- Log OnActivate gRPC receipt and handler invocation

This is temporary debug logging to diagnose why OnActivate
isn't being called for Python plugins.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:12:22 -05:00
Nick Misasi
ddbcc2d164 feat(python-sdk): export Command class and register command in example
- Export Command wrapper class from main package
- Update hello_python example to register /hello command in OnActivate
- Slash commands must be registered via api.register_command() to work

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 23:13:13 -05:00
Nick Misasi
448be3350e bench(10-03): add Python SDK benchmark tests
Create comprehensive benchmark tests measuring Python SDK overhead:

API Call Benchmarks:
- GetServerVersion: baseline gRPC round-trip
- GetUser: entity retrieval with wrapper conversion
- GetChannel: another entity pattern

Wrapper Conversion Benchmarks:
- User.from_proto / User.to_proto
- Post.from_proto
- Channel.from_proto

Hook Decorator Benchmarks:
- Hook decorator application overhead
- get_hook_name lookup (attribute access)
- is_hook_handler check

Uses simple timing with time.perf_counter() for compatibility
(pytest-benchmark not required). Reports mean, median, stdev,
min, max, and ops/sec for each benchmark.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 15:43:42 -05:00
Nick Misasi
926b6f3483 chore(10-02): add CI-friendly integration test runner scripts
Create shell scripts for consistent test invocation across CI environments:

Python script (python-sdk/scripts/run_integration_tests.sh):
- Auto-detects virtual environment or uses system Python
- Installs package in editable mode if not present
- Runs test_integration_e2e.py with verbose output
- Supports passing extra pytest arguments

Go script (server/public/pluginapi/grpc/scripts/run_integration_tests.sh):
- Runs from the correct module directory
- Filters to TestPythonPlugin and TestIntegration tests
- Disables test caching for clean CI runs
- Supports passing extra go test arguments

Both scripts:
- Are executable (chmod +x)
- Print diagnostic information (working dir, version)
- Exit with appropriate error codes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 15:36:57 -05:00
Nick Misasi
324fa65c95 test(10-02): add Python integration tests for gRPC round-trip
Create end-to-end integration tests proving Python SDK communicates
correctly with gRPC servers:

API Round-trip Tests:
- test_get_server_version_round_trip: Version API call
- test_get_system_install_date_round_trip: Install date API call
- test_get_user_round_trip: User lookup with entity serialization
- test_multiple_api_calls_in_sequence: Sequential API calls

Error Propagation Tests:
- test_not_found_error: NOT_FOUND -> NotFoundError mapping
- test_permission_denied_error: PERMISSION_DENIED mapping
- test_app_error_in_response: AppError -> PluginAPIError conversion
- test_error_recovery: Client recovery after errors

Hook Invocation Tests:
- test_implemented_returns_hooks: Implemented RPC verification
- test_lifecycle_hooks_invocation: OnActivate/OnDeactivate chain
- test_message_hook_allows_post: Normal message passthrough
- test_message_hook_rejects_spam: Message rejection
- test_message_hook_modifies_post: Message modification

Complex Scenario Tests:
- test_concurrent_api_calls: Multi-threaded API calls
- test_sequential_connect_disconnect: Connection lifecycle
- test_large_message_handling: Large payload handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 15:36:16 -05:00
Nick Misasi
11ddd32a8c test(08-02): add tests for response streaming, flush, and invalid status codes
Go tests:
- TestInvalidStatusCode_TooLow: verify status 42 returns 500 error
- TestInvalidStatusCode_TooHigh: verify status 1000 returns 500 error
- TestInvalidStatusCode_DoesNotPanic: ensure no panic for any invalid code
- TestFlush_WithFlusher: verify flush calls http.Flusher.Flush()
- TestFlush_WithoutFlusher: verify flush doesn't panic without Flusher
- TestFlushGracefulDegradation: mirror plugin/http_test.go behavior
- TestStreamResponseBody_WithFlush: verify flush flags processed in stream
- TestEarlyResponse_CancelsRequestSend: verify early response stops sender

Python tests:
- test_flush_method_exists: verify flush() is callable
- test_flush_applies_to_last_write: verify retroactive flush
- test_flush_applies_to_next_write_when_empty: verify pre-write flush
- test_multiple_writes_with_selective_flush: verify selective flushing
- test_streaming_response_chunks: verify multiple writes = multiple messages
- test_empty_body_response: verify no-body response handling
- test_response_headers_in_first_message: verify header placement

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:40:41 -05:00
Nick Misasi
00ec721e33 feat(08-02): implement Python streaming response writer with flush support
Update HTTPResponseWriter class to support streaming responses:
- Add flush() method for best-effort HTTP flush requests
- Track pending writes with flush markers for streaming mode
- Add get_pending_writes() and clear_pending_writes() methods
- Document streaming mode, header locking, and flush semantics
- Add MAX_CHUNK_SIZE constant (64KB, matching Go)

Update ServeHTTP servicer to stream responses:
- Send response chunks incrementally instead of buffering
- Include flush flag on each message when requested
- Check for cancellation between chunks
- Handle empty body case (init only)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:37:30 -05:00
Nick Misasi
5c7aecda49 feat(08-02): extend protobuf contract for response streaming + flush
Add flush field to ServeHTTPResponse for best-effort HTTP flush support.
Update ServeHTTPResponse and ServeHTTPResponseInit with comprehensive
documentation of streaming invariants, message ordering, and status
code validation rules.

Changes:
- Add flush boolean field (field 4) to ServeHTTPResponse
- Document response streaming invariants (init-once, header locking)
- Document status code validation (100-999 range, 0 defaults to 200)
- Regenerate Go and Python protobuf code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:34:36 -05:00
Nick Misasi
503ab08cbc test(08-01): add comprehensive tests for ServeHTTP streaming
Add tests for both Go and Python ServeHTTP implementations covering:
- Body chunking behavior (small, exact, multiple, large bodies)
- Context cancellation during body streaming
- Header conversion utilities
- HTTPRequest/HTTPResponseWriter helper classes
- Request body assembly from multiple chunks
- Error handling (500 for handler errors, 404 for missing handler)

Go tests:
- TestChunking_* for body chunking scenarios
- TestConvertHTTPHeaders for header conversion
- TestBuildRequestInit for request metadata building
- TestWriteResponseHeaders for response header writing
- TestContextCancellation_DuringBodyRead for cancellation

Python tests:
- TestHTTPRequest for request wrapper class
- TestHTTPResponseWriter for response writer class
- TestHeaderConversion for proto<->dict conversion
- TestServeHTTPServicer for gRPC servicer behavior
- TestChunkingBehavior for body assembly
- TestCancellation for request cancellation

Also:
- Fixed sendRequest to properly set body_complete flag on EOF
- Added ServeHTTP to HookName enum

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:31:30 -05:00
Nick Misasi
147bd106a1 feat(08-01): implement Python ServeHTTP streaming handler
Add ServeHTTP bidirectional streaming implementation to the Python
hook servicer with HTTPRequest and HTTPResponseWriter helper classes.

Key features:
- Async generator for bidirectional streaming
- HTTPRequest wrapper with method, url, headers, body
- HTTPResponseWriter mimicking Go's http.ResponseWriter pattern
- Context cancellation detection during body streaming
- Header conversion utilities for proto <-> dict

Handler pattern:
- Receives: (plugin_context, response_writer, request)
- Similar to Go's http.Handler(w, r) pattern
- Response writer collects headers/status/body

Current limitations (deferred to 08-02):
- Request body fully buffered before handler invocation
- Response body fully buffered before streaming back
- No true streaming for large bodies yet

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:26:23 -05:00
Nick Misasi
19c392084b feat(08-01): add protobuf contract for ServeHTTP request streaming
Add bidirectional streaming RPC for ServeHTTP hook to support
efficient HTTP request/response transfer between Go and Python.

Key changes:
- New hooks_http.proto with ServeHTTPRequest/Response messages
- HTTPHeader message for multi-value header support
- ServeHTTPRequestInit with full request metadata
- ServeHTTPResponseInit for status and headers
- Body chunks with completion flag for streaming
- Updated hooks.proto with ServeHTTP streaming RPC
- Regenerated Go and Python code

Design decisions:
- 64KB default chunk size per gRPC best practices
- First message carries metadata, subsequent messages carry body
- body_complete flag signals end of stream
- Headers as repeated HTTPHeader for multi-value support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:23:38 -05:00
Nick Misasi
4813f47b3f test(07-03): add comprehensive tests for remaining hooks
- Add test_hooks_command_config.py: ExecuteCommand and
  ConfigurationWillBeSaved hook tests
- Add test_hooks_user_channel.py: User lifecycle, channel/team,
  and SAML login hook tests
- Add test_hooks_notifications.py: Reaction, notification, and
  preferences hook tests
- Add test_hooks_system.py: System, WebSocket, cluster, shared
  channels, and support data hook tests
- All 65 new tests verify correct hook semantics including:
  - Fire-and-forget vs return-value hooks
  - Rejection string semantics for UserWillLogIn
  - Modify/reject semantics for notifications
  - Error handling for all hook types

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:41:41 -05:00
Nick Misasi
23eae41f7e feat(07-03): implement remaining hook methods in servicer
- Add ExecuteCommand hook with CommandResponse return semantics
- Add ConfigurationWillBeSaved with config modification support
- Add User lifecycle hooks (UserHasBeenCreated, UserWillLogIn,
  UserHasLoggedIn, UserHasBeenDeactivated)
- Add Channel/Team hooks (ChannelHasBeenCreated,
  UserHasJoined/LeftChannel, UserHasJoined/LeftTeam)
- Add Reaction hooks (ReactionHasBeenAdded, ReactionHasBeenRemoved)
- Add Notification hooks (NotificationWillBePushed,
  EmailNotificationWillBeSent) with modify/reject semantics
- Add PreferencesHaveChanged hook
- Add System hooks (OnInstall, OnSendDailyTelemetry,
  RunDataRetention, OnCloudLimitsUpdated)
- Add WebSocket hooks (OnWebSocketConnect, OnWebSocketDisconnect,
  WebSocketMessageHasBeenPosted)
- Add Cluster hook (OnPluginClusterEvent)
- Add Shared Channels hooks (OnSharedChannelsSyncMsg, OnSharedChannelsPing,
  OnSharedChannelsAttachmentSyncMsg, OnSharedChannelsProfileImageSyncMsg)
- Add Support hook (GenerateSupportData)
- Add SAML hook (OnSAMLLogin)
- Add implementation matrix comment documenting all hooks
- Document deferred Phase 8 hooks (ServeHTTP, ServeMetrics, FileWillBeUploaded)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:33:09 -05:00
Nick Misasi
ab345d7e57 test(07-02): add gRPC integration tests for hook servicer
- Add test_hooks_grpc_integration.py with in-process gRPC tests:
  - Test Implemented RPC returns correct hook list
  - Test lifecycle hooks (OnActivate, OnDeactivate, OnConfigurationChange)
  - Test MessageWillBePosted allow/reject/modify scenarios
  - Test notification hooks (MessageHasBeenPosted)
  - Test activation failure propagation via response.error
  - Test async handlers work through gRPC
- Fix lifecycle hooks to encode errors in response, not gRPC status
  - Remove context parameter from runner.invoke() for lifecycle hooks
  - Ensures activation failures are returned as AppError, not gRPC error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:27:41 -05:00
Nick Misasi
6392b981fe test(07-02): add tests for lifecycle and message hook semantics
- Add test_hooks_lifecycle.py with tests for:
  - Implemented RPC returning correct hook list
  - OnActivate success/failure propagation
  - OnDeactivate best-effort semantics
  - OnConfigurationChange error handling
- Add test_hooks_messages.py with tests for:
  - MessageWillBePosted allow/reject/modify/dismiss semantics
  - MessageWillBeUpdated with old/new post handling
  - Notification hooks (Posted/Updated/Deleted)
  - MessagesWillBeConsumed list filtering/modification
- Fix @hook decorator to preserve async handlers
  - Use separate async wrapper for coroutine functions
  - Enables proper async detection in hook runner

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:26:03 -05:00
Nick Misasi
6d2974c821 feat(07-02): implement hook servicer with lifecycle and message hooks
- Create PluginHooksServicerImpl implementing PluginHooksServicer gRPC service
- Implement Implemented RPC returning canonical hook names from registry
- Implement lifecycle hooks: OnActivate, OnDeactivate, OnConfigurationChange
- Implement message hooks: MessageWillBePosted, MessageWillBeUpdated
- Implement notification hooks: MessageHasBeenPosted/Updated/Deleted
- Implement MessagesWillBeConsumed with post list modification support
- Add DISMISS_POST_ERROR constant for silent post dismissal
- Register hook servicer in PluginServer.start()
- Document error handling convention in module docstring

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:22:56 -05:00
Nick Misasi
46a931d49d test(07-01): add plugin bootstrap smoke tests
- Test handshake line format matches go-plugin protocol
- Test server starts on ephemeral port
- Test health check responds with SERVING status
- Test RuntimeConfig loading from environment
- Add integration test for full plugin lifecycle

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:43 -05:00
Nick Misasi
cf89dd1b32 test(07-01): add hook runner unit tests
- Test sync and async handler execution
- Test timeout handling with HookTimeoutError
- Test exception wrapping in HookInvocationError
- Test gRPC status code conversion
- Test HookRunner class invocation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:38 -05:00
Nick Misasi
9cbd52e2ba test(07-01): add hook registry unit tests
- Test hook decorator with enum, string, and inferred names
- Test Plugin base class hook discovery via __init_subclass__
- Test duplicate registration error
- Test implemented_hooks() returns sorted canonical names
- Test invoke_hook() and get_hook_handler() methods

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:33 -05:00
Nick Misasi
49a7a6a121 feat(07-01): export hook system public API and add dependencies
- Export Plugin, hook, HookName, HookRegistrationError from __init__.py
- Export RuntimeConfig, load_runtime_config
- Update module docstring with plugin development example
- Add grpcio-health-checking dependency
- Add typing_extensions for Python 3.9 compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:28 -05:00
Nick Misasi
8bda0eb6ff feat(07-01): add plugin server with health service and handshake
- Create server.py with PluginServer class for gRPC server lifecycle
- Register grpc.health.v1.Health service with plugin status SERVING
- Output go-plugin handshake line (1|1|tcp|127.0.0.1:PORT|grpc)
- Bind to ephemeral port on localhost for security
- Add serve_plugin() and run_plugin() entry points

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:22 -05:00
Nick Misasi
4fb3c4ed4c feat(07-01): add runtime config loader for plugin environment
- Create runtime_config.py to load config from environment variables
- Support MATTERMOST_PLUGIN_ID, MATTERMOST_PLUGIN_API_TARGET
- Support MATTERMOST_PLUGIN_HOOK_TIMEOUT, MATTERMOST_PLUGIN_LOG_LEVEL
- Provide sensible defaults and configure_logging helper

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:16 -05:00
Nick Misasi
ef13892007 feat(07-01): add hook runner utility with timeout support
- Create hook_runner.py with run_hook_async function
- Support both sync and async handlers via asyncio.to_thread
- Enforce timeouts with asyncio.wait_for (default 30s)
- Convert exceptions to gRPC status codes
- Add HookRunner class for convenient invocation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:11 -05:00
Nick Misasi
708e033b57 feat(07-01): add Plugin base class with hook registry
- Create plugin.py with Plugin base class for plugin authors
- Use __init_subclass__ for automatic hook discovery at class definition
- Implement implemented_hooks(), has_hook(), invoke_hook() methods
- Provide api, logger, config properties for plugin instances
- Enforce single handler per hook with clear duplicate error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:05 -05:00
Nick Misasi
a959e8dec0 feat(07-01): add hook decorator and HookName enum
- Create hooks.py with @hook decorator for registration
- Add HookName enum with all canonical hook names from hooks.proto
- Support multiple decorator forms: @hook(HookName.X), @hook("X"), @hook
- Preserve function metadata with functools.wraps
- Validate hook names and raise HookRegistrationError for invalid names

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:17:59 -05:00
Nick Misasi
49fb5d8fd9 feat(06-04): implement remaining API client mixins for full RPC parity
Add comprehensive typed API client methods for all remaining Mattermost
Plugin API RPCs:

New wrapper types in wrappers.py:
- Bot, BotPatch, BotGetOptions for bot management
- Command, CommandArgs, CommandResponse for slash commands
- Preference for user preferences
- OAuthApp for OAuth applications
- Group, GroupMember, GroupSyncable for groups
- SharedChannel for shared channels
- Emoji for custom emojis
- Dialog, DialogElement, OpenDialogRequest for interactive dialogs
- AuditRecord for audit logging
- PushNotification for push notifications

New client mixins:
- BotsMixin: Bot CRUD, activation, EnsureBotUser
- CommandsMixin: Command registration/CRUD, execution, listing
- ConfigMixin: Server/plugin config, plugin management
- PreferencesMixin: User preference CRUD
- OAuthMixin: OAuth app CRUD
- GroupsMixin: Group/membership/syncable CRUD
- PropertiesMixin: Property group/field/value CRUD
- RemainingMixin: License, shared channels, dialogs, mail,
  notifications, emojis, upload sessions, post search

The audit script now reports 100% RPC coverage (236/236).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:17:32 -05:00
Nick Misasi
3974d55f20 test(06-03): add unit tests for Post/File/KV API methods
Add comprehensive tests (34 test cases) covering:
- Wrapper dataclass conversions (Post, Reaction, PostList, FileInfo)
- Post client methods (create, get, update, delete, ephemeral, reactions)
- File client methods (info, content, upload, link)
- KV store methods (set, get, delete, list, atomic operations)
- Error handling (NotFoundError, ValidationError, gRPC errors)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:36 -05:00
Nick Misasi
9bd8a450a5 feat(06-03): integrate Post/File/KV mixins into PluginAPIClient
Update PluginAPIClient to inherit from PostsMixin, FilesMixin,
and KVStoreMixin, providing 31 new typed methods for:
- Post CRUD, ephemeral posts, and reactions
- File metadata, content, and uploads
- Key-value store operations with atomic support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:30 -05:00
Nick Misasi
a0149f3dc0 feat(06-03): implement KVStoreMixin with key-value store methods
Add KVStoreMixin with 9 methods:
- kv_set, kv_get, kv_delete, kv_delete_all, kv_list
- kv_set_with_expiry
- kv_compare_and_set, kv_compare_and_delete, kv_set_with_options

Provides complete KV store functionality including atomic operations
and expiring keys for plugin data persistence.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:23 -05:00
Nick Misasi
7a171a1149 feat(06-03): implement FilesMixin with file API methods
Add FilesMixin with 8 methods:
- get_file_info, get_file_infos, set_file_searchable_content
- get_file, get_file_link, read_file
- upload_file, copy_file_infos

Covers all file metadata, content retrieval, and upload operations
following the established mixin pattern.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:17 -05:00
Nick Misasi
eaad7aff18 feat(06-03): implement PostsMixin with post and reaction methods
Add PostsMixin with 15 methods:
- create_post, get_post, update_post, delete_post
- send_ephemeral_post, update_ephemeral_post, delete_ephemeral_post
- get_post_thread, get_posts_since, get_posts_after, get_posts_before
- add_reaction, remove_reaction, get_reactions

All methods follow the established mixin pattern with proper error
handling and type annotations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:11 -05:00
Nick Misasi
31c3bd6a89 feat(06-03): add wrapper dataclasses for Post, Reaction, FileInfo, KV types
Add dataclasses for:
- Post: Mattermost message with full field support
- Reaction: User emoji reaction to a post
- PostList: Ordered list of posts with pagination
- FileInfo: File metadata with all attributes
- UploadSession: Resumable upload session
- PluginKVSetOptions: Options for KV set operations

All wrappers include from_proto() and to_proto() methods for
protobuf serialization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:04 -05:00
Nick Misasi
529e8d9a05 test(06-02): add unit tests for User/Team/Channel API methods
Add comprehensive test suite covering:

Wrapper type tests:
- User, Team, Channel from_proto/to_proto round-trips
- UserStatus, TeamMember, ChannelMember conversions

Client method tests:
- User: get_user success, not found error, gRPC error, create_user
- Team: get_team, create_team_member, get_teams_for_user
- Channel: get_channel, add_channel_member, get_direct_channel, search

Error handling tests:
- PermissionDeniedError (HTTP 403)
- ValidationError (HTTP 400)
- AlreadyExistsError (HTTP 409)

All 21 tests use mocked stubs for fast execution.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 09:55:18 -05:00
Nick Misasi
f66cfeb78d feat(06-02): add coverage audit script for RPC/method parity
Add audit_client_coverage.py that verifies all gRPC RPCs have
corresponding Python client methods:

- Extracts RPC names from generated gRPC stub source
- Compares against PluginAPIClient public methods
- Supports --include/--exclude regex filters for scoping
- Reports coverage percentage and missing methods

Usage:
  python scripts/audit_client_coverage.py --include '(User|Team|Channel)'

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 09:55:10 -05:00
Nick Misasi
f598ab0121 fix(06-02): connect mixin classes to PluginAPIClient
Add mixin imports and inheritance to PluginAPIClient so that the
User/Team/Channel API methods are actually available on the client.

This was missing from the previous mixin implementation commit.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 09:55:01 -05:00
Nick Misasi
e1dfd1ec5f feat(06-02): implement User/Team/Channel API client mixins
Add domain-specific mixin classes for PluginAPIClient:
- UsersMixin: 33 user methods (CRUD, status, auth, sessions, permissions)
- TeamsMixin: 20 team methods (CRUD, members, icons, stats)
- ChannelsMixin: 25 channel methods (CRUD, members, sidebar categories)

Each method:
- Builds correct protobuf requests from Python inputs
- Converts responses to Pythonic wrapper types
- Maps gRPC/AppError to SDK exceptions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:55:56 -05:00
Nick Misasi
4411ccbcbf feat(06-02): add wrapper dataclasses for User, Team, Channel types
Add Pythonic wrapper types for core Mattermost entities:
- User, UserStatus, CustomStatus, UserAuth, Session, UserAccessToken
- Team, TeamMember, TeamMemberWithError, TeamUnread, TeamStats
- Channel, ChannelMember, ChannelStats, SidebarCategoryWithChannels
- ViewUsersRestrictions and related enums

All wrappers implement from_proto/to_proto for protobuf conversion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:55:48 -05:00
Nick Misasi
6043b7906a test(06-01): add smoke tests for codegen and client
Add comprehensive smoke tests:
- test_codegen_imports.py: verify all generated modules import correctly
- test_client_smoke.py: test client lifecycle, error mapping, and RPC calls
- Tests use in-process fake gRPC server for integration testing
- 42 tests passing covering imports, message creation, and error handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:45:01 -05:00
Nick Misasi
0c905f4d07 feat(06-01): add generated protobuf and gRPC Python code
Generate Python code from server/public/pluginapi/grpc/proto:
- Protocol buffer message definitions (*_pb2.py)
- gRPC service stubs (*_pb2_grpc.py)
- Type stubs for IDE completion (*_pb2.pyi, *_pb2_grpc.pyi)
- Imports fixed to use package-relative imports

Generated from 18 .proto files using grpcio-tools and mypy-protobuf.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:54 -05:00
Nick Misasi
5cff580fcb feat(06-01): add sync and async Plugin API clients
Add client classes for Mattermost Plugin API:
- PluginAPIClient: sync client with context manager support
- AsyncPluginAPIClient: async client with grpc.aio support
- Both implement get_server_version() as smoke test RPC
- Proper error handling converts gRPC errors to SDK exceptions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:47 -05:00
Nick Misasi
d52baf9345 feat(06-01): add channel factory and exception hierarchy
Add core SDK runtime components:
- channel.py: gRPC channel factory with keepalive and size options
- exceptions.py: SDK exception hierarchy with gRPC error mapping
  - PluginAPIError (base), NotFoundError, PermissionDeniedError,
    ValidationError, AlreadyExistsError, UnavailableError
- convert_grpc_error() and convert_app_error() utilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:41 -05:00
Nick Misasi
aae96ff737 feat(06-01): add protobuf code generation script
Add generate_protos.py script that:
- Generates Python and gRPC code from .proto files
- Generates mypy type stubs via mypy-protobuf
- Fixes imports to use package-relative imports
- Outputs to src/mattermost_plugin/grpc/

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:35 -05:00
Nick Misasi
3cf6e36477 feat(06-01): add Python SDK package scaffold with pyproject.toml
Create the foundational Python SDK package structure:
- pyproject.toml with project metadata and dependencies
- src/mattermost_plugin/ package with src layout
- Configured for Python >=3.9 with grpcio and protobuf deps
- Dev dependencies include grpcio-tools, mypy-protobuf, pytest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:29 -05:00