Skip to content

fix(sdk): Teams adapter — encode channelId, icacls warning, integration tests#883

Merged
tamirdresher merged 1 commit intodevfrom
squad/772-teams-adapter-fixes
Apr 12, 2026
Merged

fix(sdk): Teams adapter — encode channelId, icacls warning, integration tests#883
tamirdresher merged 1 commit intodevfrom
squad/772-teams-adapter-fixes

Conversation

@diberry
Copy link
Copy Markdown
Collaborator

@diberry diberry commented Apr 6, 2026

Summary

Combined fix for three Teams adapter issues from PR #768 review follow-ups.

#771 — Encode channelId in Teams deep-link URL

\channelId\ was used unvalidated in the deep-link URL template. Applied \�ncodeURIComponent()\ to match the existing pattern used by \getNotificationUrl()\ for chat IDs.

#770 — Log warning when icacls fails on Windows token permission hardening

The \�xecFile('icacls', ...)\ callback was a no-op — failures were silently swallowed. Now logs a warning: \⚠️ Could not restrict token file permissions: {message}.

#772 — Integration tests for Teams adapter class methods

Added 10 integration tests with mocked Graph API covering:

  • Token refresh flow (expired → refresh → success)
  • Cached token reuse (no re-auth on second call)
  • \postUpdate\ to team channel (verifies encoded deep-link URL)
  • \postUpdate\ to 1:1 chat
  • \pollForReplies\ filtering out own messages
  • \pollForReplies\ error → warning + empty array
  • \pollForReplies\ channel mode composite threadId
  • \�nsureChat\ 'me' mode with explicit chatId (skips creation)
  • \�nsureChat\ 'me' mode without chatId (throws)
  • \�nsureChat\ recipientUpn mode (creates 1:1 chat via Graph POST)

Test results

\
✓ test/comms-teams.test.ts (35 tests)
✓ test/comms-teams-integration.test.ts (10 tests)
Test Files 2 passed (2)
Tests 45 passed (45)
\\

Closes #772, closes #771, closes #770

Copilot AI review requested due to automatic review settings April 6, 2026 17:30
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 6, 2026

🟡 Impact Analysis — PR #883

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 3
Files added 2
Files modified 1
Files deleted 0
Modules touched 3

🎯 Risk Factors

  • 3 files changed (≤5 → LOW)
  • 3 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • .changeset/teams-adapter-fixes.md
squad-sdk (1 file)
  • packages/squad-sdk/src/platform/comms-teams.ts
tests (1 file)
  • test/comms-teams-integration.test.ts

This report is generated automatically for every PR. See #733 for details.

Copy link
Copy Markdown
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

This PR delivers three follow-up fixes/improvements to the SDK’s Microsoft Teams communication adapter: safer channel deep-link construction, better Windows permission-hardening observability, and higher-confidence coverage via integration-style tests with mocked Graph API responses.

Changes:

  • Encode channelId in the Teams channel deep-link URL returned from postUpdate().
  • Log a warning when icacls fails during Windows token-file permission hardening.
  • Add integration tests for adapter class methods (ensureAuthenticated, ensureChat, postUpdate, pollForReplies) using mocked fetch and node:fs.

Reviewed changes

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

File Description
test/comms-teams-integration.test.ts Adds integration tests for Teams adapter class methods with mocked Graph API and token persistence.
packages/squad-sdk/src/platform/comms-teams.ts Fixes channel deep-link encoding and surfaces icacls failures via console.warn.
.changeset/teams-adapter-fixes.md Adds a patch changeset entry documenting the Teams adapter fixes and new tests.

Comment thread test/comms-teams-integration.test.ts Outdated
Comment thread test/comms-teams-integration.test.ts
@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Apr 6, 2026

✅ APPROVED (CI/CD Review)

Summary

All CI/CD gates passing. PR meets quality standards for merge.

Checklist Results

Changeset File: Present (.changeset/teams-adapter-fixes.md)
CI Status: All 9 critical checks passing (1 skipped, 0 failed)
Commit Count: 2 commits (product fix + docs update)
Build Concerns: None — all workflows succeeded

  • Policy Gates: ✓
  • Export smoke test: ✓
  • Exports map check: ✓
  • Samples build: ✓
  • Unit tests: ✓ (4m13s)
  • Repo health: ✓
  • Changes validation: ✓
  • Impact analysis: ✓
  • Publish policy: ✓

File Scope

  • 4 files changed, 381 additions, 2 deletions
  • Changes scoped to Teams adapter (SDK), tests, and EECOM history docs — reasonable scope

Verdict

APPROVE — Ready to merge from CI/CD perspective. All automation gates satisfied.

@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Apr 6, 2026

🔒 RETRO Security Review — PR #883

Verdict: ✅ APPROVE

All three changes are clean from a security perspective. No request for changes.


#771 — channelId encoding

Finding: Sufficient. The encodeURIComponent(this.config.channelId) on the deep-link URL is correctly applied. Importantly, validateGraphId() (line 623) already runs before the deep-link is constructed, so by the time we reach the URL template, the channelId has been validated against the strict allowlist /^[\w:@.\-]+$/ — encoding is defense-in-depth here, not the only barrier.

Other parameters? I checked:

  • teamId — validated via validateGraphId() before Graph API calls; not included in deep-link URLs
  • chatId — already encoded in getNotificationUrl() (line 616)
  • recipientUpn — validated via validateGraphId() before Graph API calls
  • The composite "teamId|channelId" return value is re-split and re-validated with validateGraphId() in pollForReplies (lines 568–569)

No gaps found. The encoding is consistent across all URL-facing parameters.

#770 — icacls warning log

Finding: Acceptable. The err.message from execFile may include the file path ~/.squad/teams-tokens.json, but this is a predictable, documented path — not a secret. The warning goes to console.warn (stderr), which is appropriate for operator-visible diagnostics. No token content, credentials, or user-specific data leaks through err.message.

Bonus: execFile (not exec) is used, so no shell injection risk from TOKEN_PATH or process.env.USERNAME — arguments are passed as an array, not interpolated into a shell command.

Test mock safety

Finding: Clean. All test tokens are obviously fake strings ('cached-access-token', 'expired-token', 'new-refresh-token', etc.). UPNs use @contoso.com (Microsoft's canonical test domain). No real client IDs, tenant IDs, or Graph API credentials anywhere in the fixture data.

Token refresh logic

Finding: Sound. Key observations:

  1. 60-second expiry buffer (Date.now() < expiresAt - 60_000) — prevents using near-expired tokens ✅
  2. Graceful fallback chain: cached → refresh → browser PKCE → device code — each stage catches and falls through ✅
  3. Refresh token preservation: data.refresh_token ?? refreshToken correctly keeps the old refresh token when the server doesn't rotate it ✅
  4. Token persistence permissions: writeFileSync with mode: 0o600 + explicit chmodSync/icacls hardening ✅
  5. No token logging: access/refresh tokens never appear in console.log or console.warn calls ✅

Summary: Solid security posture. The validateGraphId() allowlist + encodeURIComponent() double-defense pattern is good. Token handling follows the right practices (file-permission hardening, no logging, graceful refresh). Tests use clearly synthetic data.

— RETRO, Security Specialist

@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Apr 6, 2026

🔍 Quality Review — FIDO (Quality Owner)

Verdict: Approve with changes — production fixes look correct, but test coverage has gaps that weaken the "3 fixes + 10 tests" story.


✅ What's good


⚠️ Findings (ranked by severity)

1. Fix #770 is untested — icacls warning never fires in CI (Medium)

The icacls code path is gated by platform() === 'win32' (comms-teams.ts:71), and CI runs on Ubuntu only. The execFile mock always calls cb(null), so the console.warn path is never exercised. One of the 3 stated fixes has zero test coverage.

Suggestion: Add a test that mocks execFile to call cb(new Error('access denied')) and asserts console.warn was called with the expected message. Even on Linux, this verifies the callback logic independent of the platform gate.

2. Missing vi.unstubGlobals() — fetch leak risk (Medium)

beforeEach calls vi.stubGlobal('fetch', fetchMock) but afterEach only does vi.restoreAllMocks(). This does not unstub globals — a mocked fetch could leak to other test files depending on Vitest worker isolation.

Fix: Add vi.unstubGlobals() to afterEach, or use vi.stubGlobal with a matching vi.unstubGlobals().

3. Token refresh failure path untested (Medium)

The refresh test (test 1) only covers the success path. ensureAuthenticated() has a catch block (comms-teams.ts:441-443) that warns and falls back to browser auth when refresh fails — this branch is never tested.

Suggestion: Add a test where the /oauth2/v2.0/token endpoint returns an error response, then assert the warning and fallback behavior.

4. Fetch mocks don't verify request shape (Medium-Low)

Several tests assert return values but not the actual URL paths or HTTP methods sent to fetch. E.g., the channel-mode test proves the deep-link is encoded, but doesn't verify that fetch was called with POST to /teams/{teamId}/channels/{channelId}/messages. A routing regression could slip through.

5. Graph 429/503/504 retry logic untested (Low)

Not introduced by this PR, but the graphFetch retry loop (comms-teams.ts:92-111) has no coverage. Lower priority since it's pre-existing.

6. cachedUserId module-level state (Low)

getMyUserId() caches in a module-level variable (comms-teams.ts:388) that's never reset between tests. Currently benign but could cause order-dependent failures if new tests are added.


📊 Coverage scorecard

Fix Tests Covered?
#771 channelId encoding Test 3 ✅ Direct assertion
#770 icacls warning ❌ No test exercises error path
#772 integration tests Tests 1-10 ✅ Good breadth, gaps noted above

Bottom line

The production code changes are minimal and correct. The test suite is a strong start but oversells coverage — 1 of 3 fixes is untested and the mock isolation has a leak. Addressing findings 1-3 would make this a confident approve.

@diberry diberry force-pushed the squad/772-teams-adapter-fixes branch from 0035407 to a51cac0 Compare April 6, 2026 17:50
@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Apr 6, 2026

Addressed all three FIDO review items in a51cac0 (squashed to 1 commit):

Fix 1 — \�i.unstubAllGlobals()\ in afterEach: Added to prevent fetch stub leaking between tests.

Fix 2 — icacls warning test: New test \logs warning when icacls fails on Windows\ mocks
ode:os\ \platform()\ → 'win32',
ode:child_process\ \�xecFile\ → callback with error, triggers \saveTokens()\ via token refresh, and asserts \console.warn\ receives the icacls failure message. Exercises the Windows-only path that CI can't reach on Ubuntu.

Fix 3 — Token refresh failure test: New test \warns on refresh failure and falls back to device code\ provides an expired cached token, mocks the refresh endpoint to return \invalid_grant\ (no \�ccess_token), and verifies the adapter (a) logs the refresh warning, (b) falls through browser auth (mocked
ode:http\ \createServer\ emits immediate error), (c) completes successfully via device code flow.

All 47 Teams tests + 52 SDK tests pass. 12 integration tests total (up from 10).

@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Apr 8, 2026

Why is the comms integration file failing and why is the file name different from the thing it is testing.

@diberry diberry force-pushed the squad/772-teams-adapter-fixes branch from fb3ac33 to 4fb6155 Compare April 8, 2026 15:58
@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Apr 8, 2026

🔍 Squad Review — Kaylee (Engineering)

# Check Status Notes
1 Changelog entry .changeset/teams-adapter-fixes.md present (patch bump for squad-sdk)
2 Squashed to 1 commit 1 commit
3 CI green All checks pass (45 tests pass across both test files)
4 Copilot comments resolved 2 threads — all resolved (typo fix applied, vi.unstubGlobal handled)
5 No .squad/ files Clean
6 No unrelated files All 3 files directly related to Teams adapter fixes (#770, #771, #772)
7 Tests for changes Implementation (comms-teams.ts) has comprehensive integration tests (comms-teams-integration.test.ts — 10 tests)
8 Not a duplicate/reversal Unique scope (closes #770, #771, #772)

Verdict: ✅ Ready to merge


Review by Squad AI team (Kaylee — Engineering) · requested by Dina Berry

Copy link
Copy Markdown
Collaborator Author

@diberry diberry left a comment

Choose a reason for hiding this comment

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

required: The channelId encoding fix is correct (\�ncodeURIComponent\ is right for URL path segments), but the deep-link URL template was already structurally incomplete before this PR — it's missing the /{channelName}?groupId={teamId}&tenantId={tenantId}\ suffix. Encoding a broken template just makes it a correctly-encoded broken URL. Worth tracking as a follow-up issue if not addressing here.

suggestion: Only the icacls failure path (warning emitted) is tested. A complementary test confirming warnSpy is NOT called when execFile succeeds on win32 would guard against a future inversion of the if(err) guard.

suggestion: Test section comment numbering is inconsistent — the comment // ─── 3. postUpdate to chat vs channel\ appears before the icacls section, making it appear unnumbered.

nit: encodeURIComponent coverage is narrow — only tests 19:channel@thread.tacv2. A parameterized case with a space or # would verify the encoding contract more thoroughly, though Teams IDs don't contain these in practice.

@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Apr 10, 2026

✅ CI validation passed on fork: diberry#145 — 7/7 checks green

@tamirdresher tamirdresher merged commit a69c527 into dev Apr 12, 2026
11 checks passed
@tamirdresher tamirdresher deleted the squad/772-teams-adapter-fixes branch April 12, 2026 10:34
tamirdresher pushed a commit that referenced this pull request Apr 21, 2026
…on tests (#883)

Co-authored-by: Dina Berry <diberry@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants