ATOM Documentation

← Back to App

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/callback

2. 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:

  1. Log into your app
  2. Open browser DevTools → Application → Cookies
  3. Find the session cookie
  4. 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

  1. **Copy the authorization_url from the response**
  1. **Open it in your browser**
  1. **Sign in with your Microsoft account** (if not already signed in)
  1. **Grant permissions** when prompted - you should see:
  • Read your teams and channels
  • Read and send messages
  • Access your presence info
  • etc.
  1. **After granting, you'll be redirected to**:
  1. **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:

  1. Open Microsoft Teams
  2. Go to a team
  3. Click on "..." next to team name → "Get link to team"
  4. The URL will be like: https://teams.microsoft.com/l/team/19:TEAM_ID@thread.tacv2/
  5. 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

  1. Open Microsoft Teams
  2. Go to the channel
  3. Verify the message appears
  4. 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

  1. Go to your database (NeonDB console)
  2. Find the integration_tokens row for your tenant and provider='teams'
  3. 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)

  1. Connect Teams integration with Tenant A
  2. Try to access channels using Tenant B's credentials
  3. Verify you cannot access Tenant A's channels

Test 10.2: Verify Database Query Filtering

  1. Open database console
  2. Run this query:
  3. 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_tokens table
  • OAuth states are stored in the oauth_states table with 10-minute expiry
  • Token refresh happens automatically when tokens expire

Happy Testing! 🚀