@@ -112,3 +112,156 @@ Deno.test("uploadMediaByE2EE — file type → no preview upload at all", async
112112 } ) ;
113113 assertEquals ( fake . records . length , 1 ) ;
114114} ) ;
115+
116+ /** contentMetadata of the one sendMessage the upload ends with. */
117+ function sentMetadata (
118+ fake : ReturnType < typeof fakeClient > ,
119+ ) : Record < string , string > {
120+ assertEquals ( fake . sendMessageCalls . length , 1 ) ;
121+ return ( fake . sendMessageCalls [ 0 ] as {
122+ contentMetadata : Record < string , string > ;
123+ } ) . contentMetadata ;
124+ }
125+
126+ Deno . test ( "uploadMediaByE2EE — a video carries its length as DURATION" , async ( ) => {
127+ const { obs, fake } = makeObs ( ) ;
128+ await obs . uploadMediaByE2EE ( {
129+ data : new Blob ( [ new Uint8Array ( 1_000 ) ] ) ,
130+ oType : "video" ,
131+ to : "u-recipient" ,
132+ durationMs : 4200.4 ,
133+ } ) ;
134+ assertEquals ( sentMetadata ( fake ) . DURATION , "4200" ) ;
135+ } ) ;
136+
137+ Deno . test ( "uploadMediaByE2EE — no DURATION when omitted, none on an image" , async ( ) => {
138+ const video = makeObs ( ) ;
139+ await video . obs . uploadMediaByE2EE ( {
140+ data : new Blob ( [ new Uint8Array ( 1_000 ) ] ) ,
141+ oType : "video" ,
142+ to : "u-recipient" ,
143+ } ) ;
144+ assertEquals ( sentMetadata ( video . fake ) . DURATION , undefined ) ;
145+
146+ const image = makeObs ( ) ;
147+ await image . obs . uploadMediaByE2EE ( {
148+ data : new Blob ( [ new Uint8Array ( 1_000 ) ] ) ,
149+ oType : "image" ,
150+ to : "u-recipient" ,
151+ durationMs : 4200 ,
152+ } ) ;
153+ assertEquals ( sentMetadata ( image . fake ) . DURATION , undefined ) ;
154+ } ) ;
155+
156+ Deno . test ( "uploadMediaByE2EE — a non-positive or non-finite duration is dropped" , async ( ) => {
157+ for ( const durationMs of [ 0 , - 1 , NaN , Infinity ] ) {
158+ const { obs, fake } = makeObs ( ) ;
159+ await obs . uploadMediaByE2EE ( {
160+ data : new Blob ( [ new Uint8Array ( 1_000 ) ] ) ,
161+ oType : "video" ,
162+ to : "u-recipient" ,
163+ durationMs,
164+ } ) ;
165+ assertEquals ( sentMetadata ( fake ) . DURATION , undefined ) ;
166+ }
167+ } ) ;
168+
169+ Deno . test ( "uploadMediaByE2EE — rounded zero and unsafe durations are omitted" , async ( ) => {
170+ for (
171+ const durationMs of [
172+ 0.1 ,
173+ 0.49 ,
174+ Number . MIN_VALUE ,
175+ - Infinity ,
176+ Number . MAX_SAFE_INTEGER + 1 ,
177+ 1e21 ,
178+ Number . MAX_VALUE ,
179+ ]
180+ ) {
181+ const { obs, fake } = makeObs ( ) ;
182+ await obs . uploadMediaByE2EE ( {
183+ data : new Blob ( [ "video" ] ) ,
184+ oType : "video" ,
185+ to : "u-recipient" ,
186+ durationMs,
187+ } ) ;
188+ assertEquals (
189+ sentMetadata ( fake ) . DURATION ,
190+ undefined ,
191+ `durationMs=${ durationMs } ` ,
192+ ) ;
193+ }
194+ } ) ;
195+
196+ Deno . test ( "uploadMediaByE2EE — rounds valid durations to positive integer milliseconds" , async ( ) => {
197+ for (
198+ const [ durationMs , expected ] of [ [ 0.5 , "1" ] , [ 1 , "1" ] , [ 4200.5 , "4201" ] , [
199+ Number . MAX_SAFE_INTEGER ,
200+ "9007199254740991" ,
201+ ] ] as const
202+ ) {
203+ const { obs, fake } = makeObs ( ) ;
204+ await obs . uploadMediaByE2EE ( {
205+ data : new Blob ( [ "video" ] ) ,
206+ oType : "video" ,
207+ to : "u-recipient" ,
208+ durationMs,
209+ } ) ;
210+ assertEquals ( sentMetadata ( fake ) . DURATION , expected ) ;
211+ }
212+ } ) ;
213+
214+ Deno . test ( "uploadMediaByE2EE — duration does not change non-video metadata" , async ( ) => {
215+ for ( const oType of [ "audio" , "file" , "image" , "gif" ] as const ) {
216+ const withDuration = makeObs ( ) ;
217+ const withoutDuration = makeObs ( ) ;
218+ const options = { data : new Blob ( [ "media" ] ) , oType, to : "u-recipient" } ;
219+ await withDuration . obs . uploadMediaByE2EE ( { ...options , durationMs : 4200 } ) ;
220+ await withoutDuration . obs . uploadMediaByE2EE ( options ) ;
221+ assertEquals (
222+ sentMetadata ( withDuration . fake ) ,
223+ sentMetadata ( withoutDuration . fake ) ,
224+ ) ;
225+ }
226+ } ) ;
227+
228+ Deno . test ( "uploadMediaByE2EE — video duration preserves encrypted preview and message metadata" , async ( ) => {
229+ const { obs, fake } = makeObs ( ) ;
230+ await obs . uploadMediaByE2EE ( {
231+ data : new Blob ( [ new Uint8Array ( 1000 ) ] , { type : "video/mp4" } ) ,
232+ oType : "video" ,
233+ to : "c-group" ,
234+ filename : "clip.mp4" ,
235+ preview : new Blob ( [ new Uint8Array ( 20 ) ] ) ,
236+ durationMs : 4200 ,
237+ } ) ;
238+ const metadata = sentMetadata ( fake ) ;
239+ assertEquals ( metadata , {
240+ SID : "emv" ,
241+ OID : "OBJ-1" ,
242+ FILE_SIZE : "1032" ,
243+ e2eeVersion : "2" ,
244+ DURATION : "4200" ,
245+ MEDIA_CONTENT_INFO : JSON . stringify ( {
246+ category : "original" ,
247+ fileSize : 1032 ,
248+ extension : "mp4" ,
249+ animated : false ,
250+ } ) ,
251+ } ) ;
252+ assertEquals ( fake . records . map ( ( record ) => record . size ) , [ 1032 , 52 ] ) ;
253+ assert ( fake . records [ 0 ] . obsPath . startsWith ( "talk/emv/reqid-" ) ) ;
254+ assertEquals ( fake . records [ 1 ] . obsPath , "talk/emv/OBJ-1__ud-preview" ) ;
255+ assertEquals (
256+ fake . e2eeCalls [ 1 ] . keyMaterial ,
257+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" ,
258+ ) ;
259+ const message = fake . sendMessageCalls [ 0 ] as {
260+ to : string ;
261+ contentType : number ;
262+ chunks : Uint8Array [ ] ;
263+ } ;
264+ assertEquals ( message . to , "c-group" ) ;
265+ assertEquals ( message . contentType , 2 ) ;
266+ assertEquals ( message . chunks , [ new Uint8Array ( ) ] ) ;
267+ } ) ;
0 commit comments