@@ -40,8 +40,13 @@ function createMailByFolderAndReceivedDate(mailId: IdTuple, mailSet: IdTuple, re
4040 } )
4141}
4242
43- function createSpamTrainingDatumByConfidenceAndDecision ( confidence : string , spamDecision : SpamDecision ) : ClientSpamTrainingDatum {
43+ function createSpamTrainingDatumByConfidenceAndDecision (
44+ confidence : string ,
45+ spamDecision : SpamDecision ,
46+ id : IdTuple = [ "listId" , "elementId" ] ,
47+ ) : ClientSpamTrainingDatum {
4448 return createTestEntity ( ClientSpamTrainingDatumTypeRef , {
49+ _id : id ,
4550 _ownerGroup : "group" ,
4651 confidence,
4752 spamDecision,
@@ -153,14 +158,24 @@ o.spec("SpamClassificationDataDealer", () => {
153158 o ( "uploads training data when clientSpamTrainingData is empty" , async ( ) => {
154159 when ( entityClientMock . load ( MailboxGroupRootTypeRef , "owner" ) ) . thenResolve ( mailboxGroupRoot )
155160 when ( entityClientMock . load ( MailBoxTypeRef , "mailbox" ) ) . thenResolve ( mailBox )
156- const spamTrainingData = Array . from ( { length : 10 } , ( ) =>
157- createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . WHITELIST ) ,
158- ) . concat ( Array . from ( { length : 10 } , ( ) => createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . BLACKLIST ) ) )
159- const mails = Array . from ( { length : 10 } , ( ) =>
160- createMailByFolderAndReceivedDate ( [ mailBox . currentMailBag ! . mails , "inboxMailId" ] , inboxFolder . _id , new Date ( ) , mailDetails . _id ) ,
161+ const mails = Array . from ( { length : 10 } , ( _ , index ) =>
162+ createMailByFolderAndReceivedDate ( [ mailBox . currentMailBag ! . mails , "inboxMailId" + index ] , inboxFolder . _id , new Date ( ) , mailDetails . _id ) ,
163+ ) . concat (
164+ Array . from ( { length : 10 } , ( _ , index ) =>
165+ createMailByFolderAndReceivedDate ( [ mailBox . currentMailBag ! . mails , "spamMailId" + index ] , spamFolder . _id , new Date ( ) , mailDetails . _id ) ,
166+ ) ,
167+ )
168+ const spamTrainingData = Array . from ( { length : 10 } , ( _ , index ) =>
169+ createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . WHITELIST , [
170+ mailBox . clientSpamTrainingData ! ,
171+ getElementId ( mails [ index ] ) ,
172+ ] ) ,
161173 ) . concat (
162- Array . from ( { length : 10 } , ( ) =>
163- createMailByFolderAndReceivedDate ( [ mailBox . currentMailBag ! . mails , "spamMailId" ] , spamFolder . _id , new Date ( ) , mailDetails . _id ) ,
174+ Array . from ( { length : 10 } , ( _ , index ) =>
175+ createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . BLACKLIST , [
176+ mailBox . clientSpamTrainingData ! ,
177+ getElementId ( mails [ 10 + index ] ) ,
178+ ] ) ,
164179 ) ,
165180 )
166181 const modifiedIndicesSinceStart = spamTrainingData . map ( ( data ) =>
@@ -203,18 +218,106 @@ o.spec("SpamClassificationDataDealer", () => {
203218 } )
204219 } )
205220
221+ o ( "uploads training data when clientSpamTrainingData does not include all relevant mails" , async ( ) => {
222+ when ( entityClientMock . load ( MailboxGroupRootTypeRef , "owner" ) ) . thenResolve ( mailboxGroupRoot )
223+ when ( entityClientMock . load ( MailBoxTypeRef , "mailbox" ) ) . thenResolve ( mailBox )
224+
225+ const relevantMails = Array . from ( { length : 40 } , ( _ , index ) =>
226+ createMailByFolderAndReceivedDate ( [ mailBox . currentMailBag ! . mails , "inboxMailId" + index ] , inboxFolder . _id , new Date ( ) , mailDetails . _id ) ,
227+ ) . concat (
228+ Array . from ( { length : 40 } , ( _ , index ) =>
229+ createMailByFolderAndReceivedDate ( [ mailBox . currentMailBag ! . mails , "spamMailId" + index ] , spamFolder . _id , new Date ( ) , mailDetails . _id ) ,
230+ ) ,
231+ )
232+
233+ const existingSpamTrainingData = Array . from ( { length : 20 } , ( _ , index ) =>
234+ createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . WHITELIST , [
235+ mailBox . clientSpamTrainingData ! ,
236+ getElementId ( relevantMails [ index ] ) ,
237+ ] ) ,
238+ ) . concat (
239+ Array . from ( { length : 20 } , ( _ , index ) =>
240+ createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . BLACKLIST , [
241+ mailBox . clientSpamTrainingData ! ,
242+ getElementId ( relevantMails [ 40 + index ] ) ,
243+ ] ) ,
244+ ) ,
245+ )
246+
247+ const updatedSpamTrainingData = Array . from ( { length : 40 } , ( _ , index ) =>
248+ createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . WHITELIST , [
249+ mailBox . clientSpamTrainingData ! ,
250+ getElementId ( relevantMails [ index ] ) ,
251+ ] ) ,
252+ ) . concat (
253+ Array . from ( { length : 40 } , ( _ , index ) =>
254+ createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . BLACKLIST , [
255+ mailBox . clientSpamTrainingData ! ,
256+ getElementId ( relevantMails [ 40 + index ] ) ,
257+ ] ) ,
258+ ) ,
259+ )
260+
261+ const modifiedIndicesSinceStart = updatedSpamTrainingData . map ( ( data ) =>
262+ createClientSpamTrainingDatumIndexEntryByClientSpamTrainingDatumElementId ( getElementId ( data ) ) ,
263+ )
264+
265+ when ( entityClientMock . loadAll ( ClientSpamTrainingDatumTypeRef , mailBox . clientSpamTrainingData ! ) ) . thenResolve (
266+ existingSpamTrainingData ,
267+ updatedSpamTrainingData ,
268+ )
269+ when ( entityClientMock . loadAll ( MailTypeRef , mailBox . currentMailBag ! . mails , anything ( ) ) ) . thenResolve ( relevantMails )
270+ when ( entityClientMock . loadAll ( MailTypeRef , mailBox . archivedMailBags [ 0 ] . mails , anything ( ) ) ) . thenResolve ( [ ] )
271+ when ( entityClientMock . loadAll ( MailFolderTypeRef , mailBox . folders ! . folders ) ) . thenResolve ( [ inboxFolder , spamFolder , trashFolder ] )
272+ when ( entityClientMock . loadAll ( ClientSpamTrainingDatumIndexEntryTypeRef , mailBox . modifiedClientSpamTrainingDataIndex ! ) ) . thenResolve (
273+ modifiedIndicesSinceStart ,
274+ )
275+
276+ when ( bulkMailLoaderMock . loadMailDetails ( relevantMails ) ) . thenResolve (
277+ relevantMails . map ( ( mail ) => {
278+ return { mail, mailDetails }
279+ } ) ,
280+ )
281+
282+ const trainingDataset = await spamClassificationDataDealer . fetchAllTrainingData ( "owner" )
283+
284+ // first load: empty, second load: fetch uploaded data
285+ verify ( entityClientMock . loadAll ( ClientSpamTrainingDatumTypeRef , mailBox . clientSpamTrainingData ! ) , { times : 2 } )
286+ verify ( entityClientMock . loadAll ( ClientSpamTrainingDatumIndexEntryTypeRef , mailBox . modifiedClientSpamTrainingDataIndex ! ) , { times : 1 } )
287+
288+ const expectedUploadMailsHam = relevantMails . slice ( 20 , 40 )
289+ const expectedUploadMailsSpam = relevantMails . slice ( 60 , 80 )
290+
291+ const unencryptedPayload = expectedUploadMailsHam . concat ( expectedUploadMailsSpam ) . map ( ( mail ) => {
292+ return {
293+ mailId : mail . _id ,
294+ isSpam : isSameId ( mail . sets [ 0 ] , spamFolder . _id ) ,
295+ confidence : DEFAULT_IS_SPAM_CONFIDENCE ,
296+ vector : new Uint8Array ( 1 ) ,
297+ } as UnencryptedPopulateClientSpamTrainingDatum
298+ } )
299+ verify ( mailFacadeMock . populateClientSpamTrainingData ( "owner" , unencryptedPayload ) , { times : 1 } )
300+
301+ o ( trainingDataset ) . deepEquals ( {
302+ trainingData : updatedSpamTrainingData ,
303+ lastTrainingDataIndexId : getElementId ( last ( modifiedIndicesSinceStart ) ! ) ,
304+ hamCount : 40 ,
305+ spamCount : 40 ,
306+ } )
307+ } )
308+
206309 o ( "successfully returns training data with mixed ham/spam data" , async ( ) => {
207310 when ( entityClientMock . load ( MailboxGroupRootTypeRef , "owner" ) ) . thenResolve ( mailboxGroupRoot )
208311 when ( entityClientMock . load ( MailBoxTypeRef , "mailbox" ) ) . thenResolve ( mailBox )
312+ when ( entityClientMock . loadAll ( MailTypeRef , anything ( ) , anything ( ) ) ) . thenResolve ( [ ] )
313+
209314 const spamTrainingData = Array . from ( { length : 10 } , ( ) =>
210315 createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . WHITELIST ) ,
211316 ) . concat ( Array . from ( { length : 10 } , ( ) => createSpamTrainingDatumByConfidenceAndDecision ( DEFAULT_IS_SPAM_CONFIDENCE , SpamDecision . BLACKLIST ) ) )
212-
213317 const modifiedIndicesSinceStart = spamTrainingData . map ( ( data ) =>
214318 createClientSpamTrainingDatumIndexEntryByClientSpamTrainingDatumElementId ( getElementId ( data ) ) ,
215319 )
216320 when ( entityClientMock . loadAll ( ClientSpamTrainingDatumTypeRef , mailBox . clientSpamTrainingData ! ) ) . thenResolve ( spamTrainingData )
217- when ( entityClientMock . loadAll ( MailTypeRef , mailBox . archivedMailBags [ 0 ] . mails , anything ( ) ) ) . thenResolve ( [ ] )
218321 when ( entityClientMock . loadAll ( MailFolderTypeRef , mailBox . folders ! . folders ) ) . thenResolve ( [ inboxFolder , spamFolder , trashFolder ] )
219322 when ( entityClientMock . loadAll ( ClientSpamTrainingDatumIndexEntryTypeRef , mailBox . modifiedClientSpamTrainingDataIndex ! ) ) . thenResolve (
220323 modifiedIndicesSinceStart ,
@@ -241,6 +344,7 @@ o.spec("SpamClassificationDataDealer", () => {
241344 const validSpamData = createSpamTrainingDatumByConfidenceAndDecision ( "4" , SpamDecision . BLACKLIST )
242345 when ( entityClientMock . load ( MailboxGroupRootTypeRef , "owner" ) ) . thenResolve ( mailboxGroupRoot )
243346 when ( entityClientMock . load ( MailBoxTypeRef , "mailbox" ) ) . thenResolve ( mailBox )
347+ when ( entityClientMock . loadAll ( MailTypeRef , anything ( ) , anything ( ) ) ) . thenResolve ( [ ] )
244348
245349 const spamTrainingData = [ noneDecisionData , zeroConfData , validSpamData , validHamData ]
246350 const modifiedIndicesSinceStart = spamTrainingData . map ( ( data ) =>
0 commit comments