@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
22import type { PluginRuntime , SsrFPolicy } from "../runtime-api.js" ;
33import { readRemoteMediaResponse } from "./attachments.test-helpers.js" ;
44import { downloadMSTeamsAttachments } from "./attachments/download.js" ;
5+ import { downloadMSTeamsGraphMedia } from "./attachments/graph.js" ;
56import { resolveRequestUrl } from "./attachments/shared.js" ;
67import { setMSTeamsRuntime } from "./runtime.js" ;
78
@@ -123,6 +124,7 @@ type DownloadedMediaExpectation = { path?: string; placeholder?: string };
123124
124125const DEFAULT_MAX_BYTES = 1024 * 1024 ;
125126const DEFAULT_ALLOW_HOSTS = [ TEST_HOST ] ;
127+ const GRAPH_MESSAGE_URL = `https://${ GRAPH_HOST } /v1.0/chats/chat-id/messages/message-id` ;
126128const MEDIA_PLACEHOLDER_IMAGE = "<media:image>" ;
127129const MEDIA_PLACEHOLDER_DOCUMENT = "<media:document>" ;
128130const _formatImagePlaceholder = ( count : number ) =>
@@ -683,5 +685,68 @@ describe("msteams attachments", () => {
683685 expect ( logger . error ) . not . toHaveBeenCalled ( ) ;
684686 } ) ;
685687 } ) ;
688+
689+ describe ( "Graph hosted content value downloads" , ( ) => {
690+ it ( "skips hosted /$value payloads whose content-length exceeds maxBytes" , async ( ) => {
691+ const fetchMock = vi . fn ( async ( input : RequestInfo | URL ) => {
692+ const url = resolveRequestUrl ( input ) ;
693+ if ( url === GRAPH_MESSAGE_URL ) {
694+ return createJsonResponse ( { attachments : [ ] } ) ;
695+ }
696+ if ( url === `${ GRAPH_MESSAGE_URL } /hostedContents` ) {
697+ return _createGraphCollectionResponse ( [ { id : "hosted-oversized" } ] ) ;
698+ }
699+ if ( url === `${ GRAPH_MESSAGE_URL } /hostedContents/hosted-oversized/$value` ) {
700+ return new Response ( new Uint8Array ( PNG_BUFFER ) , {
701+ status : 200 ,
702+ headers : {
703+ "content-type" : CONTENT_TYPE_IMAGE_PNG ,
704+ "content-length" : String ( DEFAULT_MAX_BYTES + 1 ) ,
705+ } ,
706+ } ) ;
707+ }
708+ return _createGraphCollectionResponse ( [ ] ) ;
709+ } ) ;
710+
711+ const result = await downloadMSTeamsGraphMedia ( {
712+ messageUrl : GRAPH_MESSAGE_URL ,
713+ tokenProvider : createTokenProvider ( ) ,
714+ maxBytes : DEFAULT_MAX_BYTES ,
715+ fetchFn : asFetchFn ( fetchMock ) ,
716+ } ) ;
717+
718+ expect ( result . media ) . toHaveLength ( 0 ) ;
719+ expect ( saveMediaBufferMock ) . not . toHaveBeenCalled ( ) ;
720+ } ) ;
721+
722+ it ( "skips hosted /$value payloads that exceed maxBytes without content-length" , async ( ) => {
723+ const fetchMock = vi . fn ( async ( input : RequestInfo | URL ) => {
724+ const url = resolveRequestUrl ( input ) ;
725+ if ( url === GRAPH_MESSAGE_URL ) {
726+ return createJsonResponse ( { attachments : [ ] } ) ;
727+ }
728+ if ( url === `${ GRAPH_MESSAGE_URL } /hostedContents` ) {
729+ return _createGraphCollectionResponse ( [ { id : "hosted-stream-oversized" } ] ) ;
730+ }
731+ if ( url === `${ GRAPH_MESSAGE_URL } /hostedContents/hosted-stream-oversized/$value` ) {
732+ return createBufferResponse (
733+ new Uint8Array ( DEFAULT_MAX_BYTES + 1 ) ,
734+ CONTENT_TYPE_IMAGE_PNG ,
735+ ) ;
736+ }
737+ return _createGraphCollectionResponse ( [ ] ) ;
738+ } ) ;
739+
740+ const result = await downloadMSTeamsGraphMedia ( {
741+ messageUrl : GRAPH_MESSAGE_URL ,
742+ tokenProvider : createTokenProvider ( ) ,
743+ maxBytes : DEFAULT_MAX_BYTES ,
744+ fetchFn : asFetchFn ( fetchMock ) ,
745+ } ) ;
746+
747+ expect ( result . media ) . toHaveLength ( 0 ) ;
748+ expect ( saveMediaBufferMock ) . not . toHaveBeenCalled ( ) ;
749+ } ) ;
750+ } ) ;
686751 } ) ;
687752} ) ;
0 commit comments