Microsoft Teams Integration - Manual Testing Guide
Prerequisites
1. Environment Variables
Set these in your .env file or Fly.io dashboard:
MICROSOFT_CLIENT_ID=your-client-id-from-azure
MICROSOFT_CLIENT_SECRET=your-client-secret-from-azure
MICROSOFT_TENANT_ID=common # or your organization's tenant ID
MICROSOFT_REDIRECT_URI=https://atom-saas.fly.dev/api/integrations/callback2. Tools Needed
- **Postman** or **Insomnia** (recommended)
- Or **curl** for command-line testing
- **Microsoft Teams** account with admin access
- Web browser
3. Get Your Auth Token
For testing API routes that require authentication, you'll need:
- Log into your app
- Open browser DevTools → Application → Cookies
- Find the session cookie
- Or use the session token from localStorage
---
Test 1: Health Check Endpoint
Purpose
Verify Teams integration is configured and check connection status.
Test 1.1: Check Configuration (No Auth Required)
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/health"**Expected Response (Not Configured):**
{
"status": "unhealthy",
"configured": false,
"ok": false,
"details": {
"error": "Missing Microsoft credentials",
"required_env_vars": [
"MICROSOFT_CLIENT_ID",
"MICROSOFT_CLIENT_SECRET",
"MICROSOFT_TENANT_ID",
"MICROSOFT_REDIRECT_URI"
]
}
}**Expected Response (Configured but not connected):**
{
"status": "healthy",
"configured": true,
"ok": false,
"details": {
"connected": false,
"provider": "teams",
"has_credentials": true,
"message": "Teams integration is configured. Please log in and connect."
}
}**Expected Response (Connected):**
{
"status": "healthy",
"configured": true,
"ok": true,
"details": {
"connected": true,
"provider": "teams",
"has_credentials": true,
"has_token": true,
"token_valid": true,
"user_info": {
"displayName": "Your Name"
}
}
}---
Test 2: OAuth Authorization Flow
Purpose
Test the OAuth authorization URL generation and complete the flow.
Test 2.1: Generate Authorization URL
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/authorize?tenant_id=YOUR_TENANT_ID" \
-H "Content-Type: application/json"**Or via POST:**
curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/authorize" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "YOUR_TENANT_ID"
}'**Expected Response:**
{
"success": true,
"authorization_url": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=...",
"authUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=...",
"state": "BASE64_ENCODED_STATE_STRING",
"provider": "teams",
"scopes": [
"User.Read",
"Team.ReadBasic.All",
"Channel.ReadBasic.All",
"ChannelMessage.Read.All",
"Chat.Read",
"Chat.ReadWrite",
"OnlineMeetings.ReadWrite",
"Presence.Read",
"offline_access"
]
}Test 2.2: Complete OAuth Flow
- **Copy the
authorization_urlfrom the response**
- **Open it in your browser**
- **Sign in with your Microsoft account** (if not already signed in)
- **Grant permissions** when prompted - you should see:
- Read your teams and channels
- Read and send messages
- Access your presence info
- etc.
- **After granting, you'll be redirected to**:
- **If successful, you'll be redirected to**:
Test 2.3: Verify Connection
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/health"**Expected Response:**
{
"status": "healthy",
"configured": true,
"ok": true,
"details": {
"connected": true,
"has_token": true
}
}---
Test 3: List Teams Channels
Purpose
Test fetching channels from a Microsoft Team.
Test 3.1: Get Your Team ID
First, you need to find your Team ID in Microsoft Teams:
- Open Microsoft Teams
- Go to a team
- Click on "..." next to team name → "Get link to team"
- The URL will be like:
https://teams.microsoft.com/l/team/19:TEAM_ID@thread.tacv2/ - Copy the TEAM_ID part
Test 3.2: Fetch Channels
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels?workspace_id=YOUR_TEAM_ID" \
-H "Cookie: your-session-cookie"**Expected Response:**
{
"success": true,
"channels": [
{
"id": "19:CHANNEL_ID@thread.tacv2",
"name": "General",
"description": "General discussion for the team",
"type": "standard",
"createdDateTime": "2024-01-01T00:00:00Z",
"webUrl": "https://teams.microsoft.com/l/channel/19:CHANNEL_ID/General"
},
{
"id": "19:CHANNEL_ID2@thread.tacv2",
"name": "Random",
"description": "Random stuff",
"type": "private",
"createdDateTime": "2024-01-02T00:00:00Z",
"webUrl": "https://teams.microsoft.com/l/channel/19:CHANNEL_ID2/Random"
}
],
"total": 2
}Test 3.3: Test Error Cases
**Missing workspace_id:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels"Expected: 400 Bad Request with error about missing workspace_id
**Not authenticated:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels?workspace_id=xxx"Expected: 401 Unauthorized with error about tenant ID
---
Test 4: Send Message to Channel
Purpose
Test sending messages to a Teams channel.
Test 4.1: Send a Simple Message
**Request:**
curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
-H "Content-Type: application/json" \
-H "Cookie: your-session-cookie" \
-d '{
"workspace_id": "YOUR_TEAM_ID",
"channel_id": "YOUR_CHANNEL_ID",
"text": "Hello from the API! This is a test message."
}'**Expected Response:**
{
"success": true,
"message_id": "MESSAGE_ID",
"channel_id": "YOUR_CHANNEL_ID",
"team_id": "YOUR_TEAM_ID",
"webUrl": "https://teams.microsoft.com/l/channel/MESSAGE_ID"
}Test 4.2: Send Message with Importance
**Request:**
curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
-H "Content-Type: application/json" \
-H "Cookie: your-session-cookie" \
-d '{
"workspace_id": "YOUR_TEAM_ID",
"channel_id": "YOUR_CHANNEL_ID",
"text": "This is an urgent message!",
"importance": "high"
}'Test 4.3: Send HTML Formatted Message
**Request:**
curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
-H "Content-Type: application/json" \
-H "Cookie: your-session-cookie" \
-d '{
"workspace_id": "YOUR_TEAM_ID",
"channel_id": "YOUR_CHANNEL_ID",
"text": "<div><b>Bold text</b> and <i>italic text</i></div>"
}'Test 4.4: Reply to Existing Thread
First, get a message_id from the history endpoint, then:
**Request:**
curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
-H "Content-Type: application/json" \
-H "Cookie: your-session-cookie" \
-d '{
"workspace_id": "YOUR_TEAM_ID",
"channel_id": "YOUR_CHANNEL_ID",
"thread_id": "PARENT_MESSAGE_ID",
"text": "This is a reply to the thread."
}'Test 4.5: Verify in Teams
- Open Microsoft Teams
- Go to the channel
- Verify the message appears
- Check formatting and importance
---
Test 5: Fetch Message History
Purpose
Test fetching historical messages from a channel.
Test 5.1: Get Recent Messages (Default Limit: 50)
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/history/YOUR_CHANNEL_ID?workspace_id=YOUR_TEAM_ID" \
-H "Cookie: your-session-cookie"**Expected Response:**
{
"success": true,
"messages": [
{
"id": "MESSAGE_ID",
"ts": "2024-04-21T10:30:00Z",
"timestamp": "2024-04-21T10:30:00Z",
"content": {
"body": "<div><div><div><div>Message content here</div></div></div></div>"
},
"text": "Message content here",
"direction": "inbound",
"bot_id": null,
"status": "delivered",
"sender": {
"id": "USER_ID",
"name": "John Doe",
"email": "john@example.com"
},
"reply_to_id": null,
"channel_id": "YOUR_CHANNEL_ID",
"team_id": "YOUR_TEAM_ID"
}
],
"total": 50,
"has_more": true,
"next_link": "https://graph.microsoft.com/v1.0/..."
}Test 5.2: Get Specific Number of Messages
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/history/YOUR_CHANNEL_ID?workspace_id=YOUR_TEAM_ID&limit=10" \
-H "Cookie: your-session-cookie"Test 5.3: Pagination Test
If has_more: true, you can fetch more (note: next_link is for reference, you'd implement full pagination in production):
# For now, increase limit or the API handles the first page---
Test 6: Analytics Endpoint
Purpose
Test fetching analytics data for Teams integration.
Test 6.1: Get All-Time Analytics
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/analytics" \
-H "Cookie: your-session-cookie"**Expected Response:**
{
"success": true,
"analytics": {
"message_statistics": [
{
"direction": "inbound",
"count": 150
},
{
"direction": "outbound",
"count": 45
}
],
"conversation_statistics": {
"total_conversations": 25,
"active_conversations": 12
},
"period": {
"start_date": null,
"end_date": null
}
}
}Test 6.2: Get Date-Ranged Analytics
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/analytics?start_date=2024-04-01&end_date=2024-04-21" \
-H "Cookie: your-session-cookie"---
Test 7: Disconnect Integration
Purpose
Test disconnecting the Teams integration.
Test 7.1: Disconnect
**Request:**
curl -X DELETE "https://atom-saas.fly.dev/api/v1/integrations/teams/disconnect" \
-H "Cookie: your-session-cookie"**Expected Response:**
{
"success": true,
"message": "Microsoft Teams disconnected successfully",
"deleted_tokens": 1
}Test 7.2: Verify Disconnection
**Request:**
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/health"**Expected Response:**
{
"status": "unhealthy",
"configured": true,
"ok": false,
"details": {
"connected": false
}
}---
Test 8: Token Refresh (Advanced)
Purpose
Test automatic token refresh when tokens expire.
Test 8.1: Simulate Expired Token
- Go to your database (NeonDB console)
- Find the integration_tokens row for your tenant and provider='teams'
- Manually update expires_at to a past date:
Test 8.2: Make an API Request
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels?workspace_id=YOUR_TEAM_ID" \
-H "Cookie: your-session-cookie"**Expected Behavior:**
- The API should automatically refresh the token
- The request should succeed
- Check server logs for
[Teams Channels] Token refreshed successfully
---
Test 9: Error Handling Tests
Test 9.1: Invalid Team ID
curl -X GET "https://atom-saas.fly.dev/api/v1/integrations/teams/channels?workspace_id=invalid-team-id" \
-H "Cookie: your-session-cookie"Expected: 403 Forbidden or error from Microsoft Graph API
Test 9.2: Invalid Channel ID
curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
-H "Content-Type: application/json" \
-H "Cookie: your-session-cookie" \
-d '{
"workspace_id": "YOUR_TEAM_ID",
"channel_id": "invalid-channel-id",
"text": "Test"
}'Expected: 404 Not Found or error from Microsoft Graph API
Test 9.3: Missing Required Fields
curl -X POST "https://atom-saas.fly.dev/api/v1/integrations/teams/message" \
-H "Content-Type: application/json" \
-H "Cookie: your-session-cookie" \
-d '{
"workspace_id": "YOUR_TEAM_ID"
}'Expected: 400 Bad Request with error about missing fields
---
Test 10: Security & Tenant Isolation
Purpose
Verify tenant isolation works correctly.
Test 10.1: Cross-Tenant Test (Optional, if you have multiple tenants)
- Connect Teams integration with Tenant A
- Try to access channels using Tenant B's credentials
- Verify you cannot access Tenant A's channels
Test 10.2: Verify Database Query Filtering
- Open database console
- Run this query:
- Verify tokens are properly separated by tenant_id
---
Postman Collection Example
Save this as Teams Integration.postman_collection.json:
{
"info": {
"name": "Teams Integration",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{
"key": "base_url",
"value": "https://atom-saas.fly.dev"
},
{
"key": "tenant_id",
"value": "YOUR_TENANT_ID"
},
{
"key": "team_id",
"value": "YOUR_TEAM_ID"
},
{
"key": "channel_id",
"value": "YOUR_CHANNEL_ID"
}
],
"item": [
{
"name": "Health Check",
"request": {
"method": "GET",
"url": "{{base_url}}/api/v1/integrations/teams/health"
}
},
{
"name": "Get Authorization URL",
"request": {
"method": "GET",
"url": "{{base_url}}/api/v1/integrations/teams/authorize?tenant_id={{tenant_id}}"
}
},
{
"name": "List Channels",
"request": {
"method": "GET",
"url": "{{base_url}}/api/v1/integrations/teams/channels?workspace_id={{team_id}}"
}
},
{
"name": "Send Message",
"request": {
"method": "POST",
"url": "{{base_url}}/api/v1/integrations/teams/message",
"body": {
"mode": "raw",
"raw": "{\n \"workspace_id\": \"{{team_id}}\",\n \"channel_id\": \"{{channel_id}}\",\n \"text\": \"Test message from Postman\"\n}"
}
}
},
{
"name": "Get Message History",
"request": {
"method": "GET",
"url": "{{base_url}}/api/v1/integrations/teams/history/{{channel_id}}?workspace_id={{team_id}}&limit=10"
}
},
{
"name": "Get Analytics",
"request": {
"method": "GET",
"url": "{{base_url}}/api/v1/integrations/teams/analytics"
}
},
{
"name": "Disconnect",
"request": {
"method": "DELETE",
"url": "{{base_url}}/api/v1/integrations/teams/disconnect"
}
}
]
}---
Troubleshooting
Issue: "Tenant ID required"
**Solution:** Make sure you're logged in and include your session cookie in requests.
Issue: "Teams not connected"
**Solution:** Run the OAuth authorize flow first to connect your Teams account.
Issue: "Token expired"
**Solution:** The token refresh should happen automatically. If not, disconnect and reconnect.
Issue: "Missing Microsoft credentials"
**Solution:** Set up the required environment variables (MICROSOFT_CLIENT_ID, etc.)
Issue: "Access denied / Forbidden"
**Solution:** Check that your Microsoft app has the required permissions and the Team ID is correct.
---
Testing Checklist
Print this checklist and mark each item as you test:
Configuration
- [ ] Environment variables are set
- [ ] Health check returns "configured: true"
OAuth Flow
- [ ] Authorization URL generates correctly
- [ ] Can sign in to Microsoft
- [ ] Permissions are requested and granted
- [ ] Redirect to success page happens
- [ ] Tokens are stored in database
Channels
- [ ] Can list channels from a team
- [ ] Channel data includes all expected fields
- [ ] Works with multiple teams
Messages
- [ ] Can send a simple message
- [ ] Message appears in Teams
- [ ] Can send HTML formatted message
- [ ] Can set importance level
- [ ] Can reply to existing thread
History
- [ ] Can fetch message history
- [ ] Messages are in correct order (newest first)
- [ ] Limit parameter works
- [ ] Sender information is included
Analytics
- [ ] Can fetch analytics
- [ ] Message statistics are correct
- [ ] Date range filtering works
Disconnect
- [ ] Can disconnect integration
- [ ] Tokens are removed from database
- [ ] Cannot make API calls after disconnect
Token Refresh
- [ ] Expired token is automatically refreshed
- [ ] API call succeeds after refresh
Security
- [ ] Cannot access another tenant's data
- [ ] Tenant ID is properly filtered in queries
- [ ] Error messages don't leak sensitive data
---
Notes
- All API endpoints require authentication (session-based or header)
- The integration uses Microsoft Graph API v1.0
- Tokens are stored in the
integration_tokenstable - OAuth states are stored in the
oauth_statestable with 10-minute expiry - Token refresh happens automatically when tokens expire
Happy Testing! 🚀