import {test} from 'node:test'; import assert from 'node:assert/strict'; import {readFile} from 'node:fs/promises'; import ts from 'typescript'; import {runInNewContext} from 'node:vm'; // Run the bundled SDK's real named-event validators without initializing it: // accepted events stay in its pre-init queue and never contact an analytics server. const sdkLogs=[]; const sdkSandbox={console:{warn:(...args)=>sdkLogs.push(args)}}; runInNewContext(await readFile(new URL('../public/scripts/web-sdk-v1.2.0.js',import.meta.url),'utf8'),sdkSandbox); async function moduleUrl(file,replace=code=>code){ const code=ts.transpileModule(await readFile(new URL(file,import.meta.url),'utf8'),{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.ESNext}}).outputText; return 'data:text/javascript;base64,'+Buffer.from(replace(code)).toString('base64'); } const trackingUrl=await moduleUrl('../src/lib/tracking.ts'); const reportUrl=await moduleUrl('../src/lib/analytics/report-fields.ts'); const {TRACKER_APP_ID,COMMON_KEYS,SDK_COMMON_KEYS,base64JSON,trackingPage}=await import(trackingUrl); const authUrl='data:text/javascript,export function authMetadata(){return {channel:"paid_1050",devID:"business-device",deviceId:"business-device"}}'; const userAgentUrl=await moduleUrl('../src/lib/api/user-agent.ts'); const analyticsUrl=await moduleUrl('../src/lib/client/analytics.ts',code=>code.replace("'../tracking'",JSON.stringify(trackingUrl)).replace("'./auth'",JSON.stringify(authUrl)).replace("'../api/user-agent'",JSON.stringify(userAgentUrl)).replace("'../analytics/report-fields'",JSON.stringify(reportUrl))); const {SiteTracker}=await import(analyticsUrl); const {trackingHeaders}=await import(await moduleUrl('../src/lib/api/tracking-headers.ts',code=>code.replace("'../tracking'",JSON.stringify(trackingUrl)))); const flush=()=>new Promise(resolve=>setImmediate(resolve)); const common=()=>({app_id:TRACKER_APP_ID,uid:'',channel:'paid_1050',sid:'sid-raw',client_ts:Math.floor(Date.now()/1000),device:'PC',device_id:'sdk-device',device_brand:'',device_model:'',device_fingerprint:'raw-fingerprint',fp_version:'3.0.1',user_agent:'test UA',sdk_version:'1.2.0',app_version:'1.0.0'}); class Element extends EventTarget { dataset={};hidden=false;attributes=new Map(); getClientRects(){return this.hidden?[]:[{}];} closest(selector){ if(selector.includes('[hidden]'))return this.hidden||this.closedDialog?this:null; if(selector==='[data-track-item]'&&this.hasAttribute('data-track-item'))return this; if(selector==='[data-track-ad="external"]'&&this.getAttribute('data-track-ad')==='external')return this; if(selector.includes('a[data-app-download]')&&this.hasAttribute('data-app-download'))return this; if(selector.includes('a[data-track-navigation]')&&this.dataset.trackNavigation)return this; return this.parents?.[selector]||null; } getAttribute(name){return this.attributes.get(name)||null;} hasAttribute(name){return this.attributes.has(name);} } class Video extends Element {currentTime=0;duration=100;playbackRate=1.25;paused=false;ended=false;seeking=false;} function fixture(){ const saved=new Map(),events=[],calls=[],nodes=[],results=[],diagnostics=[],observed=new Set(),consoleCalls=[]; let clock=1000,video,intersection,mutation,modal=null; const fields=common(); const sdk={init(options){calls.push(options);},track:new Proxy({}, {get:(_,method)=>async payload=>{events.push({method,payload});const result=JSON.parse(JSON.stringify(await sdkSandbox.WebSDK.track[method](payload)));results.push({method,...result});return result;}}),getCommonFields:async()=>({...fields}),setUid(uid){fields.uid=uid;},setChannel(channel){fields.channel=channel;},notifyPageLoad(){calls.push('route');},flush:async()=>{calls.push('flush');}}; const win=new EventTarget();win.WebSDK=sdk;win.console={warn:(...args)=>consoleCalls.push(args)}; const document=new EventTarget();Object.assign(document,{referrer:'https://search.example/',documentElement:new Element(),head:{append(){}},querySelector:selector=>selector==='dialog:modal'?modal:video||null,querySelectorAll:()=>nodes}); document.addEventListener('tracker:diagnostic',event=>diagnostics.push(event.detail)); const values={window:win,document,location:{pathname:'/',search:'',origin:'https://site.example',href:'https://site.example/'},performance:{now:()=>clock,getEntriesByType:()=>[{duration:42}]},Element,HTMLVideoElement:Video,IntersectionObserver:class{constructor(callback){intersection=callback;}observe(node){observed.add(node);}unobserve(node){observed.delete(node);}disconnect(){observed.clear();}},MutationObserver:class{constructor(callback){mutation=callback;}observe(){}},innerWidth:1000,innerHeight:800,screen:{width:1000,height:800}}; for(const [key,value] of Object.entries(values)){saved.set(key,Object.getOwnPropertyDescriptor(globalThis,key));Object.defineProperty(globalThis,key,{configurable:true,writable:true,value});} const tracker=new SiteTracker();win.tracker=tracker; return {tracker,events,calls,fields,nodes,win,document,results,diagnostics,observed,sdk,consoleCalls,location:values.location,mutate:()=>mutation([]),setModal:value=>{modal=value;},setClock:value=>{clock=value;},setVideo:value=>{video=value;},intersect:node=>intersection([{target:node,isIntersecting:true}]),close(){for(const [key,descriptor] of saved)if(descriptor)Object.defineProperty(globalThis,key,descriptor);else delete globalThis[key];}}; } const drama={id:23456,title:'短剧标题',totalEpisode:12,themeIds:[8],themeNames:['都市'],tagIds:[9],tagNames:['爱情']}; const chapters=[{id:90001,currentEpisode:1,mediaTime:100},{id:90002,currentEpisode:2,mediaTime:100}]; function advertisement(placement='inline'){ const node=new Element();node.attributes.set('data-track-ad','external'); node.dataset={adId:'same-code',adSlotKey:'slot-3',adSlotName:'推荐应用',adType:'icon',adClickable:'1',adPlacement:placement}; return node; } test('real SDK accepts content exposures, clicks, search and navigation fields including the root parent id',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); const item=new Element();item.attributes.set('data-track-item','');item.dataset={itemId:'23456',itemType:'short_drama',itemName:'剧名'}; f.intersect(item);f.tracker.click({target:item,clientX:10,clientY:10}); f.tracker.search('短剧',28,1); const nav=new Element();nav.dataset.trackNavigation='tg_group';nav.textContent='官方TG群';nav.href='https://t.me/example';nav.attributes.set('href',nav.href); f.tracker.click({target:nav,clientX:10,clientY:10}); await flush(); for(const method of ['appPageView','appPageitemExposure','appPageitemClick','keywordSearch','navigation','pageClick'])assert.ok(f.results.some(result=>result.method===method&&result.ok),method); assert.ok(f.events.filter(e=>e.method.startsWith('appPageitem')).every(e=>e.payload.parent_item_id==='0')); assert.deepEqual(f.results.filter(result=>!result.ok),[]);assert.deepEqual(f.diagnostics,[]); }finally{f.close();} }); test('home, large-popup and grid-popup advertisements report independently without duplicating each placement',async()=>{ const f=fixture();try{ const nodes=['inline','startup-ad','startup-grid'].map(advertisement); // Popup events can arrive before the asynchronous SDK configuration. for(const node of nodes){f.tracker.externalAd(node,'show');f.tracker.externalAd(node,'show');f.tracker.externalAd(node,'click');} assert.equal(f.events.length,0); f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); assert.equal(f.events.filter(e=>e.method==='adImpression').length,3);assert.equal(f.events.filter(e=>e.method==='adClick').length,3); assert.ok(f.events.filter(e=>e.method==='adImpression').every(e=>e.payload.ad_slot_key==='slot-3'),'retain API advertisement slot codes'); assert.deepEqual(f.results.filter(result=>!result.ok),[]); const count=f.events.length;f.tracker.externalAd(nodes[1],'close');assert.equal(f.events.length,count,'closing is not an ad click'); for(const node of nodes.slice(1)){f.tracker.advertising(node,'close');f.tracker.advertising(node,'close');} const popup=f.events.filter(e=>e.method==='advertising'); assert.equal(popup.filter(e=>e.payload.event_type==='show').length,2); assert.equal(popup.filter(e=>e.payload.event_type==='click').length,2); assert.equal(popup.filter(e=>e.payload.event_type==='close').length,2); await flush();assert.deepEqual(f.results.filter(result=>!result.ok),[]); }finally{f.close();} }); test('closed dialogs and obscured background content are observed again when the modal closes',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); const background=advertisement(),grid=advertisement('startup-grid'),offscreen=advertisement('offscreen');offscreen.dataset.adId='offscreen'; f.nodes.push(background,grid,offscreen);grid.closedDialog=true;f.mutate(); const modal={contains:node=>node===grid||node===offscreen};f.setModal(modal);grid.closedDialog=false;f.mutate(); assert.equal(f.observed.has(background),false);assert.ok(f.observed.has(grid)); f.intersect(background);assert.equal(f.events.filter(e=>e.method==='adImpression').length,0); f.intersect(grid);assert.equal(f.events.filter(e=>e.method==='adImpression').length,1); assert.equal(f.events.some(e=>e.payload.ad_id==='offscreen'),false,'a scrollable grid must only expose intersecting tiles'); f.intersect(offscreen);assert.equal(f.events.filter(e=>e.method==='adImpression').length,2); grid.closedDialog=true;offscreen.closedDialog=true;f.setModal(null);f.mutate(); assert.ok(f.observed.has(background));f.intersect(background);assert.equal(f.events.filter(e=>e.method==='adImpression').length,3); f.document.hidden=true;const hidden=advertisement('hidden');hidden.dataset.adId='hidden';const before=f.events.length; f.tracker.externalAd(hidden,'show');f.intersect(hidden);assert.equal(f.events.length,before); f.nodes.push(hidden);f.document.hidden=false;f.document.dispatchEvent(new Event('visibilitychange')); assert.ok(f.observed.has(hidden));f.intersect(hidden);assert.equal(f.events.filter(e=>e.method==='adImpression').length,4); }finally{f.close();} }); test('SDK validation errors and denied event codes produce sanitized diagnostics instead of disappearing silently',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); const broken=new Element();broken.attributes.set('data-track-item','');broken.dataset={itemId:'7',itemType:'short_drama'}; f.intersect(broken);await flush();assert.equal(f.diagnostics[0].reason,'event_validation_failed');assert.ok(f.diagnostics[0].missingFields.includes('item_name')); f.sdk.track={adImpression:async()=>({ok:false,reason:'event_code_not_allowed',encryptedConfig:'never-log-cipher',payload:{uid:'never-log-uid'}})}; f.tracker.externalAd(advertisement(),'show');await flush(); assert.equal(f.diagnostics.at(-1).reason,'event_code_not_allowed');assert.ok(!JSON.stringify(f.diagnostics).includes('never-log')); f.tracker.externalAd(advertisement('next'),'show');await flush();assert.equal(f.diagnostics.filter(d=>d.reason==='event_code_not_allowed').length,1); assert.deepEqual(f.consoleCalls,[]);assert.deepEqual(sdkLogs,[]); }finally{f.close();} }); test('bootstrap SDK configuration requests bypass SDK readiness, and delayed config preserves more than 200 queued events',async()=>{ const f=fixture();try{ f.sdk.getCommonFields=()=>{throw Error('configuration must not depend on SDK fields');}; assert.deepEqual(await f.tracker.requestHeaders('config/info','GET',{}),{}); for(let n=0;n<220;n++){const node=advertisement();node.dataset.adId='ad-'+n;f.tracker.externalAd(node,'show');} f.tracker.externalAd(advertisement('startup-grid'),'click'); f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); assert.equal(f.events.filter(e=>e.method==='adImpression').length,221);assert.equal(f.events.filter(e=>e.method==='adClick').length,1); assert.deepEqual(f.results.filter(result=>!result.ok),[]); }finally{f.close();} }); test('all playback behavior and episode status payloads pass the real SDK validator',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); f.tracker.setDrama(drama,chapters[0],chapters); const video=new Video(),mode=new Element();mode.dataset.playMode='main';video.parents={'[data-play-mode]':mode};f.setVideo(video); for(const behavior of ['ep_play','ep_pause','ep_forward','ep_rewind','ep_complete','ep_share','ep_failed'])f.tracker.dramaEvent(behavior,video); for(const behavior of ['ep_previous','ep_next','ep_switch','ep_unlock','ep_auto_play'])f.tracker.dramaStatusChange(chapters[1],behavior); await flush();assert.deepEqual(f.results.filter(result=>!result.ok),[]);assert.deepEqual(f.diagnostics,[]); assert.ok(f.results.some(result=>result.method==='dramaEvent'));assert.ok(f.results.some(result=>result.method==='dramaStatusChange')); }finally{f.close();} }); test('SDK loading can recover after the first script was unavailable and a failed init leaves queued events retryable',async()=>{ const f=fixture();try{ delete f.win.WebSDK;let loaded=0; f.document.createElement=()=>({remove(){}}); f.document.head.append=script=>{loaded++;queueMicrotask(()=>{f.win.WebSDK=f.sdk;script.onload();});}; const initialize=f.sdk.init;let fail=true; f.sdk.init=options=>{if(fail)throw Error('private-config-must-not-appear');return initialize(options);}; f.tracker.externalAd(advertisement('startup-ad'),'show');f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); assert.equal(loaded,1);assert.equal(f.events.length,0);assert.equal(f.diagnostics[0].reason,'sdk_initialization_failed'); fail=false;f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); assert.equal(f.events.filter(e=>e.method==='appPageView').length,1);assert.equal(f.events.filter(e=>e.method==='adImpression').length,1); assert.ok(!JSON.stringify(f.diagnostics).includes('private-config')); }finally{f.close();} }); test('SDK initializes once from dynamic config, queues the first PV and tracks each native route once',async()=>{ const f=fixture();try{ f.tracker.configure({});await flush();assert.equal(f.calls.length,0);assert.equal(f.events.length,0); f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); assert.equal(f.calls[0].appId,TRACKER_APP_ID);assert.equal(f.calls[0].encryptedConfig,'configured-cipher');assert.deepEqual(f.calls[0].disableAutoEvents,['app_page_view','page_click']); assert.equal(f.events.filter(e=>e.method==='appPageView').length,1); f.tracker.configure({reportSdkConfig:'configured-cipher'});f.tracker.pageView();assert.equal(f.events.length,1); f.location.pathname='/rank/new/page/2/';f.tracker.pageView(); const pv=f.events.at(-1);assert.equal(pv.payload.page_key,'rank_new');assert.equal(pv.payload.referrer_page_key,'home');assert.equal(f.calls.filter(c=>c==='route').length,1); f.tracker.syncUser({userId:123456});assert.equal(f.fields.uid,'123456');f.tracker.syncUser(null);assert.equal(f.fields.uid,''); for(const path of ['/navigation/','/info/about-us/','/member/settings/','/topics/3/','/video/8/','/richtext/关于我们/','/richetext/关于我们/'])assert.notEqual(trackingPage(path).key,'internal_other'); assert.equal(trackingPage('/richtext/关于我们/').key,'rich_text'); const count=f.events.filter(e=>e.method==='appPageView').length; f.tracker.resumePage();assert.equal(f.events.filter(e=>e.method==='appPageView').length,count+1); f.tracker.pageView();assert.equal(f.events.filter(e=>e.method==='appPageView').length,count+1,'ordinary bindings must not duplicate the restored page view'); }finally{f.close();} }); test('ad visibility and clicks deduplicate across DOM replacements, resetting only on a new page',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); const ad=new Element();ad.attributes.set('data-track-ad','external');ad.dataset={adId:'advertise-code',adSlotKey:'slot',adSlotName:'广告位',adType:'banner',adClickable:'1'}; ad.hidden=true;f.intersect(ad);assert.equal(f.events.filter(e=>e.method==='adImpression').length,0); ad.hidden=false;f.intersect(ad);f.tracker.externalAd(ad,'show');f.tracker.externalAd(ad,'click'); assert.equal(f.events.filter(e=>e.method==='adImpression').length,1);assert.equal(f.events.filter(e=>e.method==='adClick').length,1); f.location.pathname='/shorts/';f.tracker.pageView();f.tracker.externalAd(ad,'show');assert.equal(f.events.filter(e=>e.method==='adImpression').length,2); f.location.pathname='/';f.tracker.pageView();f.tracker.externalAd(ad,'show');assert.equal(f.events.filter(e=>e.method==='adImpression').length,2,'returning to a previously seen tab must not repeat its ads'); }finally{f.close();} }); test('play durations exclude buffering and preview, speed is scaled, and seek/completion/status use named events',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); f.tracker.setDrama(drama,chapters[0],chapters); const video=new Video(),mode=new Element();mode.dataset.playMode='main';video.parents={'[data-play-mode]':mode};f.setVideo(video); const emit=type=>f.tracker.videoEvent({type,target:video}); emit('play');emit('playing');f.setClock(5000);emit('waiting');f.setClock(15000);emit('playing');f.setClock(18000);video.paused=true;video.currentTime=30;emit('pause'); const pause=f.events.filter(e=>e.payload.play_behavior_key==='ep_pause').at(-1);assert.equal(pause.payload.play_duration,7);assert.equal(pause.payload.play_speed,125);assert.equal(pause.payload.play_progress,30); const count=f.events.length;mode.dataset.playMode='preview';emit('play');emit('playing');assert.equal(f.events.length,count);mode.dataset.playMode='main'; video.currentTime=20;emit('timeupdate');emit('seeking');video.currentTime=60;emit('seeked');assert.equal(f.events.at(-1).payload.play_behavior_key,'ep_forward'); video.currentTime=100;video.ended=true;emit('ended');assert.equal(f.events.at(-2).payload.play_behavior_key,'ep_complete');assert.equal(f.events.at(-1).payload.play_behavior_key,'ep_play');assert.equal(f.events.at(-1).payload.play_progress,100); f.tracker.dramaStatusChange(chapters[1]);assert.equal(f.events.at(-1).payload.drama_behavior_key,'ep_next');f.tracker.dramaStatusChange(chapters[1],'ep_unlock');assert.equal(f.events.at(-1).payload.drama_behavior_key,'ep_unlock'); assert.ok(!f.events.some(e=>['dramaLike','dramaCollect','dramaPurchase','dramaComment'].includes(e.method))); }finally{f.close();} }); test('request headers preserve SDK values and snapshot the selected purchase chapter independently of route changes',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush();f.tracker.setDrama(drama,chapters[0],chapters); const pending=f.tracker.requestHeaders('playlet/chapter/unlock','POST',{playletId:23456,chapterId:90002}); f.location.pathname='/shorts/';f.tracker.pageView(); const headers=await pending,context=JSON.parse(Buffer.from(headers['X-DC-Request-Context'],'base64url')); assert.deepEqual(context.common,f.fields);assert.equal(context.drama.drama_id,'23456');assert.equal(context.drama.video_id,'90002');assert.equal(context.drama.episode,2); assert.equal(context.event_name,'drama_purchase');assert.match(context.request_id,/^[a-f0-9]{32}$/);assert.equal(context.request_id,headers['X-Tracker-Request-Id']); assert.deepEqual(JSON.parse(Buffer.from(headers['X-SDK-Common'],'base64')),f.fields); const videoComment=await f.tracker.requestHeaders('comment/send','POST',{objectId:2,objectType:20});assert.equal(videoComment['X-DC-Request-Context'],undefined); const order=await f.tracker.requestHeaders('coin/buy','POST',{});assert.ok(order['X-SDK-Common']);assert.equal(order['X-Tracker-Event-Name'],undefined); }finally{f.close();} }); test('terminal playback errors report failed once per attempt, exclude preview and use the video dictionary',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush(); f.tracker.setDrama(drama,chapters[0],chapters); const video=new Video(),mode=new Element();mode.dataset.playMode='main';video.parents={'[data-play-mode]':mode};f.setVideo(video); f.tracker.videoEvent({type:'playing',target:video});f.setClock(5000);video.currentTime=30; f.tracker.playbackFailed(video);f.tracker.playbackFailed(video); const failed=f.events.filter(e=>e.payload.play_behavior_key==='ep_failed');assert.equal(failed.length,1);assert.equal(failed[0].payload.play_behavior_name,'播放失败');assert.equal(failed[0].payload.play_duration,4); mode.dataset.playMode='preview';f.tracker.prepareDramaModeChange();f.tracker.playbackFailed(video);assert.equal(f.events.filter(e=>e.payload.play_behavior_key==='ep_failed').length,1); mode.dataset.playMode='main';f.tracker.prepareDramaModeChange();f.tracker.playbackFailed(video);assert.equal(f.events.filter(e=>e.payload.play_behavior_key==='ep_failed').length,2); f.tracker.setDrama(drama,null,[],true);assert.equal(f.events.at(-1).payload.video_behavior_name,'视频展示'); f.tracker.prepareDramaModeChange();f.tracker.playbackFailed(video);assert.equal(f.events.at(-1).method,'videoEvent');assert.equal(f.events.at(-1).payload.video_behavior_key,'video_failed');assert.equal(f.events.at(-1).payload.video_behavior_name,'播放失败'); }finally{f.close();} }); test('completion includes 100 percent play progress while preserving the final playback interval',async()=>{ const f=fixture();try{ f.tracker.configure({reportSdkConfig:'configured-cipher'});await flush();f.tracker.setDrama(drama,chapters[0],chapters); const video=new Video(),mode=new Element();mode.dataset.playMode='main';video.parents={'[data-play-mode]':mode};f.setVideo(video); f.tracker.videoEvent({type:'playing',target:video});f.setClock(9000);video.currentTime=100;video.ended=true;f.tracker.videoEvent({type:'ended',target:video}); const [complete,progress]=f.events.slice(-2);assert.equal(complete.payload.play_behavior_key,'ep_complete');assert.equal(progress.payload.play_behavior_key,'ep_play'); assert.equal(complete.payload.play_progress,100);assert.equal(progress.payload.play_progress,100);assert.equal(complete.payload.play_duration,8);assert.equal(progress.payload.play_duration,8); const count=f.events.length;f.tracker.reportProgress();assert.equal(f.events.length,count,'the final playback interval must be consumed after progress reporting'); }finally{f.close();} }); test('all SDK public fields reach login and order requests unchanged including numeric timestamps and optional system metadata',async()=>{ const f=fixture();try{ Object.assign(f.fields,{device_brand:'品牌',device_model:'model',system_name:'test-system',system_version:'1.2.3'}); for(const path of ['auth/login','auth/register','coin/buy','vip/pay']){ const headers=await f.tracker.requestHeaders(path,'POST',{}),forwarded=trackingHeaders(new Headers(headers),path,'POST'); assert.deepEqual(JSON.parse(Buffer.from(forwarded['X-SDK-Common'],'base64')),f.fields);assert.equal(forwarded['X-DC-Request-Context'],undefined); assert.match(headers['X-User-Agent'],/^DevID=business-device;DevType=PC;SysType=h5_pc;Ver=1.0.0;BuildID=web;/); } }finally{f.close();} }); test('the BFF accepts only the correct route, app, common fields and context schema without blocking business data',()=>{ const fields=common(),requestId='1234567890abcdef1234567890abcdef'; const context={v:1,request_id:requestId,event_name:'drama_purchase',common:fields,drama:{media_id:'',drama_id:'23456',episode:2,video_id:'90002',recommend_trace_id:''}}; const source=new Headers({'X-SDK-Common':base64JSON(Object.fromEntries(SDK_COMMON_KEYS.map(k=>[k,fields[k]]))),'X-Tracker-Request-Id':requestId,'X-Tracker-Event-Name':'drama_purchase','X-DC-Request-Context':base64JSON(context,true)}); const forwarded=trackingHeaders(source,'playlet/chapter/unlock','POST');assert.deepEqual(JSON.parse(Buffer.from(forwarded['X-DC-Request-Context'],'base64url')),context); assert.equal(JSON.parse(Buffer.from(forwarded['X-SDK-Common'],'base64')).sid,'sid-raw');assert.equal(forwarded.Authorization,undefined); for(const [path,method] of [['playlet/collect/set','POST'],['config/info','GET'],['playlet/chapter/unlock','GET']])assert.equal(trackingHeaders(source,path,method)['X-DC-Request-Context'],undefined); for(const invalid of [{...context,request_id:'other'},{...context,common:{...fields,app_id:'JHA-1050'}},{...context,common:{...fields,token:'secret'}},{...context,drama:{...context.drama,episode:0}}]){ const headers=new Headers(source);headers.set('X-DC-Request-Context',base64JSON(invalid,true));const result=trackingHeaders(headers,'playlet/chapter/unlock','POST');assert.equal(result['X-DC-Request-Context'],undefined);assert.ok(result['X-SDK-Common']); } assert.deepEqual(trackingHeaders(new Headers({'X-SDK-Common':'invalid','X-DC-Request-Context':'x'.repeat(9000)}),'playlet/chapter/unlock','POST'),{}); });