Replace project with current server release and Windows setup
Import the 2026-10-09 source release, Fastify/SQLite backend, restored source assets, and analytics-enabled server build. Replace files from the previous release while retaining the existing repository history. Make builds portable across Windows and Linux, configure local startup, normalize Windows static asset paths, and fix test fixture cleanup and file URL handling. Exclude local environment settings, databases, dependencies, validation output, and TypeScript build cache from version control. Validation: npm ci, static and server builds, 106 arrival tests, and actual local HTTP checks passed. The full suite passed 467 of 470 tests; three tests still require QA fixtures omitted from the supplied release, documented in README-서버실행.md.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import assert from'node:assert/strict';import{test}from'node:test';import{loadTypescript}from'./support/load-typescript.mjs';
|
||||
const load=()=>loadTypescript(new URL('../src/input/PhotoMetadata.ts',import.meta.url));
|
||||
function jpeg(orientation,width=4032,height=3024){const bytes=new Uint8Array(51),v=new DataView(bytes.buffer);bytes.set([255,216,255,225,0,34,69,120,105,102,0,0,73,73,42,0,8,0,0,0,1,0,18,1,3,0,1,0,0,0,orientation,0,0,0,0,0,0,0,255,192,0,11,8],0);v.setUint16(43,height);v.setUint16(45,width);bytes.set([1,1,17,0],47);return bytes;}
|
||||
test('JPEG EXIF1–8 dimensions swap only on transpose orientations',async()=>{const{readPhotoMetadata}=await load();for(let o=1;o<=8;o++){const m=await readPhotoMetadata(new Blob([jpeg(o)]));assert.equal(m.orientation,o);assert.deepEqual([m.width,m.height],o>=5?[3024,4032]:[4032,3024]);}});
|
||||
test('PNG and WebP header dimensions do not decode full image',async()=>{const{readPhotoMetadata}=await load();const p=new Uint8Array(24);p.set([137,80,78,71,13,10,26,10]);new DataView(p.buffer).setUint32(16,8000);new DataView(p.buffer).setUint32(20,6000);assert.equal((await readPhotoMetadata(new Blob([p]))).width,8000);const w=new Uint8Array(30);w.set(new TextEncoder().encode('RIFFxxxxWEBPVP8X'));w[24]=255;w[25]=15;w[27]=255;w[28]=7;assert.deepEqual(await readPhotoMetadata(new Blob([w])),{width:4096,height:2048,orientation:1});});
|
||||
test('truncated/malformed metadata safely falls back and read budget is bounded',async()=>{const{readPhotoMetadata,PHOTO_HEADER_BYTES}=await load();assert.equal(await readPhotoMetadata(new Blob([new Uint8Array(3)])),null);let length;await readPhotoMetadata({slice(start,end){length=end-start;return new Blob([new Uint8Array(3)]);}});assert.ok(length<=256*1024);assert.equal(length,PHOTO_HEADER_BYTES);});
|
||||
test('known large file requests its canonical dimensions on the first decoder invocation',async()=>{const old=globalThis.createImageBitmap,calls=[];globalThis.createImageBitmap=async(_source,opts)=>{calls.push(opts);return{width:opts.resizeWidth,height:opts.resizeHeight,close(){}};};try{const{createPhotoSnapshot}=await loadTypescript(new URL('../src/input/PhotoSnapshot.ts',import.meta.url));const result=await createPhotoSnapshot(new Blob([jpeg(6)]),1,new AbortController().signal);assert.equal(calls.length,1);assert.deepEqual([calls[0].resizeWidth,calls[0].resizeHeight],[768,1024]);assert.deepEqual([result.width,result.height],[768,1024]);}finally{globalThis.createImageBitmap=old;}});
|
||||
test('unknown PNG/WebP orientation never forces raw-aspect decoder resize',async()=>{const old=globalThis.createImageBitmap,calls=[];const p=new Uint8Array(24);p.set([137,80,78,71,13,10,26,10]);new DataView(p.buffer).setUint32(16,4032);new DataView(p.buffer).setUint32(20,3024);globalThis.createImageBitmap=async(_source,opts)=>{calls.push(opts);return calls.length===1?{width:3024,height:4032,close(){}}:{width:768,height:1024,close(){}};};try{const{createPhotoSnapshot}=await loadTypescript(new URL('../src/input/PhotoSnapshot.ts',import.meta.url));const result=await createPhotoSnapshot(new Blob([p]),1,new AbortController().signal);assert.equal(calls[0].resizeWidth,undefined);assert.equal(calls[1].resizeWidth,768);assert.equal(result.width,768);}finally{globalThis.createImageBitmap=old;}});
|
||||
function tiff(o){const b=new Uint8Array(26),v=new DataView(b.buffer);b.set([73,73,42,0,8,0,0,0,1,0,18,1,3,0,1,0,0,0,o]);return b;}
|
||||
function chunk(type,data,png){const b=new Uint8Array(8+data.length+(png?4:data.length%2)),v=new DataView(b.buffer);b.set(new TextEncoder().encode(type),png?4:0);v.setUint32(png?0:4,data.length,!png);b.set(data,8);return b;}
|
||||
test('PNG/WebP EXIF after large encoded payload is read with bounded slices for rotations and mirrors',async()=>{const{readPhotoMetadata,PHOTO_HEADER_BYTES}=await load();for(const png of[true,false])for(let o=1;o<=8;o++){let bytes;if(png){const ihdr=new Uint8Array(13),v=new DataView(ihdr.buffer);v.setUint32(0,8000);v.setUint32(4,6000);bytes=new Blob([new Uint8Array([137,80,78,71,13,10,26,10]),chunk('IHDR',ihdr,true),chunk('IDAT',new Uint8Array(1024*1024),true),chunk('eXIf',tiff(o),true),chunk('IEND',new Uint8Array(),true)]);}else{const vp8x=new Uint8Array(10);vp8x[0]=8;vp8x[4]=63;vp8x[5]=31;vp8x[7]=111;vp8x[8]=23;const body=new Blob([chunk('VP8X',vp8x,false),chunk('VP8 ',new Uint8Array(1024*1024),false),chunk('EXIF',tiff(o),false)]),head=new Uint8Array(12);head.set(new TextEncoder().encode('RIFF'));new DataView(head.buffer).setUint32(4,body.size+4,true);head.set(new TextEncoder().encode('WEBP'),8);bytes=new Blob([head,body]);}let read=0;const file={size:bytes.size,slice(start,end){read+=end-start;return bytes.slice(start,end);}};const metadata=await readPhotoMetadata(file);assert.deepEqual(metadata,{width:o>=5?6000:8000,height:o>=5?8000:6000,orientation:o});assert.ok(read<=2*PHOTO_HEADER_BYTES);}});
|
||||
test('JPEG without EXIF positively records orientation1 when scan reaches image data',async()=>{const{readPhotoMetadata}=await load(),b=new Uint8Array([255,216,255,192,0,11,8,11,208,15,160,1,1,17,0,255,218,0,2]);assert.deepEqual(await readPhotoMetadata(new Blob([b])),{width:4000,height:3024,orientation:1});});
|
||||
Reference in New Issue
Block a user