Cancel vault rereading without persisting a false document error
This commit is contained in:
@@ -545,3 +545,13 @@ Teklif Asistanı dosya okuması görünümün yaşam süresi sinyalini kullanır
|
|||||||
Testler: bekleyen OCR sırasında iptalde tek sayfa ve temizlik; geciken sayfa ediniminde temizlik; geç sonuçta iptal callback'inin ikinci kez çağrılmaması; normal tamamlanma sonrasında dinleyici temizliği. Mevcut gerçek 45 sayfalık PDF okuma testi de geçti. Gerçek tarayıcıda Tesseract çalışanı sırasında iptal kabulü bu turda yapılmadı.
|
Testler: bekleyen OCR sırasında iptalde tek sayfa ve temizlik; geciken sayfa ediniminde temizlik; geç sonuçta iptal callback'inin ikinci kez çağrılmaması; normal tamamlanma sonrasında dinleyici temizliği. Mevcut gerçek 45 sayfalık PDF okuma testi de geçti. Gerçek tarayıcıda Tesseract çalışanı sırasında iptal kabulü bu turda yapılmadı.
|
||||||
|
|
||||||
Sonuç: Tüm 122 test ve üretim derlemesi geçti. Büyük paket uyarıları sürüyor. Planın kalan kapsamı açık; canlı yayın yapılmadı.
|
Sonuç: Tüm 122 test ve üretim derlemesi geçti. Büyük paket uyarıları sürüyor. Planın kalan kapsamı açık; canlı yayın yapılmadı.
|
||||||
|
|
||||||
|
### D01 / D06 — Depodan yeniden okumada iptal ve özel metin koruması
|
||||||
|
|
||||||
|
Kayıtlı belge okuyucusu isteğe bağlı iptal sinyalini indirme, yanıt gövdesi ve PDF/OCR çıkarımına taşır. Başlangıç oturumu sabitlenir; belge metni döndürülmeden ve PDF işlemine geçilmeden güncel oturum kontrol edilir. DocumentVault yeniden çıkarımı ekranın yaşam süresi sinyalini kullanır; ekran kapanınca okuma durdurulur.
|
||||||
|
|
||||||
|
Yeniden çıkarımda kullanıcı iptali gerçek okuma hatası gibi kaydedilmez. İptal ve hesap değişimi sonrası `read-error` veya ortak kalem yayınına devam edilmez. Ağ/dosya hatasının mevcut sürüm kontrollü hata kaydı davranışı korunur.
|
||||||
|
|
||||||
|
Test: gerçek yardımcı fonksiyon üzerinden iptal edilen DOCX okumasında yalnız bir metin isteği, sıfır hata kaydı/kalem yayını; hesap değişimi sonrasında gelen özel Office metninin reddi. Diğer belge ekranları ve çalışan Office sunucu çıkarımının iptali bu turda tamamlanmadı; tarayıcıdan depo kapanış kabulü açık.
|
||||||
|
|
||||||
|
Sonuç: Tüm 124 test ve üretim derlemesi geçti. Büyük paket uyarıları sürüyor. Plan henüz tamamlanmadı; canlı yayın yapılmadı.
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ export const DocumentVault = () => {
|
|||||||
|
|
||||||
const DocumentVaultSession = ({signedIn}) => {
|
const DocumentVaultSession = ({signedIn}) => {
|
||||||
const live = useRef(true);
|
const live = useRef(true);
|
||||||
|
const readLifetime = useRef(new AbortController());
|
||||||
const session = useRef(sessionGuard(getToken));
|
const session = useRef(sessionGuard(getToken));
|
||||||
const current = () => live.current && session.current();
|
const current = () => live.current && session.current();
|
||||||
useEffect(() => { live.current = true; return () => { live.current = false; }; }, []);
|
useEffect(() => { live.current = true; readLifetime.current = new AbortController(); return () => { live.current = false; readLifetime.current.abort(); }; }, []);
|
||||||
const useOwnedState = (initial) => {
|
const useOwnedState = (initial) => {
|
||||||
const [value, setValue] = useState(initial);
|
const [value, setValue] = useState(initial);
|
||||||
const setOwned = useCallback((next) => { if (current()) setValue(next); }, []);
|
const setOwned = useCallback((next) => { if (current()) setValue(next); }, []);
|
||||||
@@ -47,7 +48,7 @@ const DocumentVaultSession = ({signedIn}) => {
|
|||||||
if (!current()) return;
|
if (!current()) return;
|
||||||
setBusy(true); setHata(null); setIslemeBilgi(null);
|
setBusy(true); setHata(null); setIslemeBilgi(null);
|
||||||
try {
|
try {
|
||||||
const result = await retryDocumentExtraction(document);
|
const result = await retryDocumentExtraction(document,{signal:readLifetime.current.signal});
|
||||||
setIslemeBilgi(`${document.ad}: ${result.sayi || 0} kalem çıkarıldı.`);
|
setIslemeBilgi(`${document.ad}: ${result.sayi || 0} kalem çıkarıldı.`);
|
||||||
} catch (error) { setHata(error.message); }
|
} catch (error) { setHata(error.message); }
|
||||||
finally {
|
finally {
|
||||||
|
|||||||
@@ -1,35 +1,45 @@
|
|||||||
|
import {awaitWithAbort} from './abortable.js';
|
||||||
import { getToken } from './auth.js';
|
import { getToken } from './auth.js';
|
||||||
import { reportDocumentReadError } from './account.js';
|
import { reportDocumentReadError } from './account.js';
|
||||||
|
|
||||||
export const readableDocument = (name) => /\.(pdf|txt|md|docx|xlsx|zip)$/i.test(String(name || ''));
|
export const readableDocument = (name) => /\.(pdf|txt|md|docx|xlsx|zip)$/i.test(String(name || ''));
|
||||||
|
|
||||||
export async function inspectStoredDocument(document) {
|
export async function inspectStoredDocument(document,{signal}={}) {
|
||||||
|
const token=getToken();
|
||||||
|
const check=()=>{signal?.throwIfAborted();if(!token || token!==getToken())throw new Error('Oturum değişti; belgeyi yeniden açın.');};
|
||||||
|
check();
|
||||||
if (!readableDocument(document.ad)) throw new Error('Bu dosya biçiminden metin çıkarımı desteklenmiyor.');
|
if (!readableDocument(document.ad)) throw new Error('Bu dosya biçiminden metin çıkarımı desteklenmiyor.');
|
||||||
const office = /\.(docx|xlsx|zip)$/i.test(document.ad);
|
const office = /\.(docx|xlsx|zip)$/i.test(document.ad);
|
||||||
const response = await fetch(`/api/me/documents/${office ? 'text' : 'download'}?id=${encodeURIComponent(document.id)}`, {
|
const response = await fetch(`/api/me/documents/${office ? 'text' : 'download'}?id=${encodeURIComponent(document.id)}`, {
|
||||||
headers: {Authorization:`Bearer ${getToken()}`},
|
signal, headers: {Authorization:`Bearer ${token}`},
|
||||||
});
|
});
|
||||||
if (office) {
|
if (office) {
|
||||||
const result = await response.json().catch(() => ({}));
|
const result = await awaitWithAbort(response.json(),signal);
|
||||||
|
check();
|
||||||
if (!response.ok || !result.success) throw new Error(result.error || 'Belge metni alınamadı.');
|
if (!response.ok || !result.success) throw new Error(result.error || 'Belge metni alınamadı.');
|
||||||
return {text:result.metin,format:document.ad.split('.').pop()};
|
return {text:result.metin,format:document.ad.split('.').pop()};
|
||||||
}
|
}
|
||||||
if (!response.ok) throw new Error('Belge indirilemedi.');
|
if (!response.ok) throw new Error('Belge indirilemedi.');
|
||||||
const blob = await response.blob();
|
const blob = await awaitWithAbort(response.blob(),signal);
|
||||||
|
check();
|
||||||
const { extractText } = await import('./pdfText');
|
const { extractText } = await import('./pdfText');
|
||||||
return extractText(new File([blob],document.ad,{type:document.mime || 'application/octet-stream'}));
|
check();
|
||||||
|
const result=await extractText(new File([blob],document.ad,{type:document.mime || 'application/octet-stream'}),{signal});
|
||||||
|
check();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readStoredDocument(document) { return (await inspectStoredDocument(document)).text; }
|
export async function readStoredDocument(document,options) { return (await inspectStoredDocument(document,options)).text; }
|
||||||
|
|
||||||
export async function retryDocumentExtraction(document) {
|
export async function retryDocumentExtraction(document,{signal}={}) {
|
||||||
if (document.tur !== 'ihale' || !document.ikn) throw new Error('İhaleye bağlı ihale dokümanı gerekli.');
|
if (document.tur !== 'ihale' || !document.ikn) throw new Error('İhaleye bağlı ihale dokümanı gerekli.');
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
const checkSession = () => { if (!token || token !== getToken()) throw new Error('Oturum değişti; belgeyi yeniden açın.'); };
|
const checkSession = () => { signal?.throwIfAborted(); if (!token || token !== getToken()) throw new Error('Oturum değişti; belgeyi yeniden açın.'); };
|
||||||
checkSession();
|
checkSession();
|
||||||
let text;
|
let text;
|
||||||
try { text = await readStoredDocument(document); }
|
try { text = await readStoredDocument(document,{signal}); }
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
if(error.name === 'AbortError') throw error;
|
||||||
checkSession();
|
checkSession();
|
||||||
try { await reportDocumentReadError(document.id,document.isleme); }
|
try { await reportDocumentReadError(document.id,document.isleme); }
|
||||||
catch { throw new Error(`${error.message} (hata durumu kaydedilemedi; listeyi yenileyin)`); }
|
catch { throw new Error(`${error.message} (hata durumu kaydedilemedi; listeyi yenileyin)`); }
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {test} from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {retryDocumentExtraction,inspectStoredDocument} from '../src/services/storedDocument.js';
|
||||||
|
test('cancelling stored document reading never writes a read error or publishes items',async()=>{
|
||||||
|
const storage=Object.getOwnPropertyDescriptor(globalThis,'localStorage'),original=globalThis.fetch;
|
||||||
|
Object.defineProperty(globalThis,'localStorage',{configurable:true,value:{getItem:()=>'qa-token'}});
|
||||||
|
const c=new AbortController();const calls=[];
|
||||||
|
try {
|
||||||
|
globalThis.fetch=async(url,{signal})=>{calls.push(url);return new Promise((resolve,reject)=>{
|
||||||
|
signal.addEventListener('abort',()=>reject(new DOMException('Aborted','AbortError')),{once:true});c.abort();
|
||||||
|
});};
|
||||||
|
await assert.rejects(retryDocumentExtraction({id:'doc',ad:'source.docx',tur:'ihale',ikn:'2026/123'},{signal:c.signal}),{name:'AbortError'});
|
||||||
|
assert.deepEqual(calls,['/api/me/documents/text?id=doc']);
|
||||||
|
} finally {globalThis.fetch=original;if(storage)Object.defineProperty(globalThis,'localStorage',storage);else delete globalThis.localStorage;}
|
||||||
|
});
|
||||||
|
test('late private office text is discarded when account changes',async()=>{
|
||||||
|
const storage=Object.getOwnPropertyDescriptor(globalThis,'localStorage'),original=globalThis.fetch;let token='first';
|
||||||
|
Object.defineProperty(globalThis,'localStorage',{configurable:true,value:{getItem:()=>token}});
|
||||||
|
try {
|
||||||
|
globalThis.fetch=async()=>{token='second';return Response.json({success:true,metin:'Private source'});};
|
||||||
|
await assert.rejects(inspectStoredDocument({id:'doc',ad:'source.docx'}),/Oturum değişti/);
|
||||||
|
} finally {globalThis.fetch=original;if(storage)Object.defineProperty(globalThis,'localStorage',storage);else delete globalThis.localStorage;}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user