wip: tests2

This commit is contained in:
sug 2025-10-14 18:11:25 +02:00 committed by abp
parent 1bc8ebdc4e
commit 3f09b0f5db
No known key found for this signature in database
GPG key ID: 791D4EC38A7AA7C2
5 changed files with 53 additions and 11 deletions

View file

@ -242,6 +242,12 @@ export class MailModel {
mailFolderAfterInboxRuleAndSpamProcessing.then((targetFolder) => { mailFolderAfterInboxRuleAndSpamProcessing.then((targetFolder) => {
this._showNotification(targetFolder ?? initialMailFolder, mail) this._showNotification(targetFolder ?? initialMailFolder, mail)
}) })
} else if (isUpdateForTypeRef(MailTypeRef, update) && update.operation === OperationType.DELETE) {
const mailId: IdTuple = [update.instanceListId, update.instanceListId]
// todo: how can we get the group in case of delete events?
const mailOwnerGroup = this.logins.getUserController().getMailGroupMemberships().at(0)!.group
await this.spamHandler().dropClassificationData(mailOwnerGroup, mailId)
} }
} }
} }

View file

@ -69,6 +69,10 @@ export class SpamClassificationHandler {
return assertNotNull(folderSystem.getSystemFolderByType(classifierMailSetTarget), `Could not get System folder for owner: ${mail._ownerGroup}`) return assertNotNull(folderSystem.getSystemFolderByType(classifierMailSetTarget), `Could not get System folder for owner: ${mail._ownerGroup}`)
} }
public async dropClassificationData(mailOwnerGroup: Id, mailId: IdTuple) {
await this.spamClassifier?.deleteSpamClassification(mailOwnerGroup, mailId)
}
public async updateSpamClassificationData(events: ReadonlyArray<EntityUpdateData>, mail: Mail, folderSystem: FolderSystem) { public async updateSpamClassificationData(events: ReadonlyArray<EntityUpdateData>, mail: Mail, folderSystem: FolderSystem) {
// TODO: // TODO:
// would be nice to still update spam classification data even if spam classifier is not there yet, // would be nice to still update spam classification data even if spam classifier is not there yet,

View file

@ -200,6 +200,18 @@ export class OfflineStoragePersistence {
await this.sqlCipherFacade.run(query, params) await this.sqlCipherFacade.run(query, params)
} }
async deleteSpamClassificationData(owner: Id, mailId: IdTuple): Promise<void> {
const mailListId = listIdPart(mailId)
const mailElementId = elementIdPart(mailId)
const { query, params } = sql`
DELETE
FROM spam_classification_training_data
where ownerGroup = ${owner}
AND listId = ${mailListId}
AND elementId = ${mailElementId}`
await this.sqlCipherFacade.run(query, params)
}
async updateSpamClassificationData(id: IdTuple, isSpam: boolean, isSpamConfidence: number): Promise<void> { async updateSpamClassificationData(id: IdTuple, isSpam: boolean, isSpamConfidence: number): Promise<void> {
const { query, params } = sql` const { query, params } = sql`
UPDATE spam_classification_training_data UPDATE spam_classification_training_data

View file

@ -399,6 +399,10 @@ export class SpamClassifier {
return this.offlineStorage.storeSpamClassification(spamTrainMailDatum) return this.offlineStorage.storeSpamClassification(spamTrainMailDatum)
} }
public deleteSpamClassification(ownerGroup: Id, mailId: IdTuple) {
return this.offlineStorage.deleteSpamClassificationData(ownerGroup, mailId)
}
/* /*
* TODO: Only for internal release * TODO: Only for internal release
* *
@ -598,17 +602,17 @@ export class SpamClassifier {
return concatenated.length > 0 ? concatenated : " " return concatenated.length > 0 ? concatenated : " "
} }
private async retrainModelFromScratch(storage: CacheStorage, ownerGroup: Id, cutoffTimestamp: number) { private async retrainModelFromScratch(storage: CacheStorage, ownerGroup: Id, cutoffTimestamp: number) {
console.log("Model is being re-trained from scratch, deleting old data") console.log("Model is being re-trained from scratch, deleting old data")
try { try {
await assertNotNull(this.offlineStorage).deleteSpamClassificationTrainingDataBeforeCutoff(cutoffTimestamp, ownerGroup) await assertNotNull(this.offlineStorage).deleteSpamClassificationTrainingDataBeforeCutoff(cutoffTimestamp, ownerGroup)
} catch (e) { } catch (e) {
console.error("Failed delete old training data: ", e) console.error("Failed delete old training data: ", e)
return return
} }
await this.trainFromScratch(storage, ownerGroup) await this.trainFromScratch(storage, ownerGroup)
} }
// === Testing methods // === Testing methods
public async cloneClassifier(): Promise<SpamClassifier> { public async cloneClassifier(): Promise<SpamClassifier> {

View file

@ -190,7 +190,7 @@ o.spec("MailModelTest", function () {
const inboxRuleTargetFolder = createTestEntity(MailFolderTypeRef, { _id: ["folderListId", "inboxRuleTarget"] }) const inboxRuleTargetFolder = createTestEntity(MailFolderTypeRef, { _id: ["folderListId", "inboxRuleTarget"] })
when(spamClassificationHandler.downloadMail(anything())).thenResolve(mail) when(spamClassificationHandler.downloadMail(anything())).thenResolve(mail)
when(inboxRuleHandler.findAndApplyMatchingRule(anything(), anything(), anything())).thenResolve(inboxRuleTargetFolder) when(inboxRuleHandler.findAndApplyMatchingRule(anything(), mail, anything())).thenResolve(inboxRuleTargetFolder)
const mailCreateEvent = makeUpdate({ instanceListId: "mailListId", instanceId: "mailId", operation: OperationType.CREATE }) const mailCreateEvent = makeUpdate({ instanceListId: "mailListId", instanceId: "mailId", operation: OperationType.CREATE })
await model.entityEventsReceived([mailCreateEvent]) await model.entityEventsReceived([mailCreateEvent])
@ -198,6 +198,22 @@ o.spec("MailModelTest", function () {
verify(spamClassificationHandler.predictSpamForNewMail(anything(), mail, anything()), { times: 1 }) verify(spamClassificationHandler.predictSpamForNewMail(anything(), mail, anything()), { times: 1 })
verify(spamClassifier.predict(anything()), { times: 0 }) verify(spamClassifier.predict(anything()), { times: 0 })
}) })
o("deletes a training datum for deleted mail event", async () => {
const mail = createTestEntity(MailTypeRef, {
_id: ["mailListId", "mailId"],
_ownerGroup: "owner",
mailDetailsDraft: ["draftListId", "draftId"],
mailDetails: null,
})
when(spamClassificationHandler.downloadMail(anything())).thenResolve(mail)
when(inboxRuleHandler.findAndApplyMatchingRule(anything(), anything(), anything())).thenResolve(null)
const mailDeleteEvent = makeUpdate({ instanceListId: "mailListId", instanceId: "mailId", operation: OperationType.DELETE })
await model.entityEventsReceived([mailDeleteEvent])
verify(spamClassifier.deleteSpamClassification("owner", mail._id), { times: 0 })
})
}) })
function makeUpdate({ function makeUpdate({