mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 16:07:12 +00:00
fix: save the attachment itself to Files on Stalwart 0.16, under a name the folder has free
This commit is contained in:
@@ -199,6 +199,51 @@ live('live Stalwart: composer and sending (0.16 and 1.0)', () => {
|
||||
expect(saved.attachments.map((a) => a.name)).toContain('from-files.txt');
|
||||
}, 60_000);
|
||||
|
||||
const text = (buf: ArrayBuffer) => new TextDecoder().decode(new Uint8Array(buf));
|
||||
const nodeBlob = async (id: string) => {
|
||||
const res = await singleton.request(
|
||||
[['FileNode/get', { accountId: singleton.accountId, ids: [id] }, '0']],
|
||||
[CAPABILITIES.CORE, CAPABILITIES.FILES],
|
||||
);
|
||||
return res.methodResponses[0][1].list[0] as { blobId: string; name: string };
|
||||
};
|
||||
|
||||
// 0.16 made a FileNode from a part's blobId hold the whole raw message.
|
||||
it("files a message's attachment part as the attachment's bytes, under a free name, and attaches it back", async () => {
|
||||
const content = `part ${RUN}`;
|
||||
const upload = await uploadBytes(new TextEncoder().encode(content), 'text/plain');
|
||||
const draft = await saveDraftVersion({
|
||||
from: [{ email: USER }], to: [{ email: USER }], subject: `part ${tag}`, textBody: 'see attached',
|
||||
attachments: [{ blobId: upload.blobId, name: 'part.txt', type: 'text/plain', disposition: 'attachment' }],
|
||||
}, drafts);
|
||||
createdEmails.push(draft.id);
|
||||
const [full] = await getFullEmails([draft.id]);
|
||||
const part = full.attachments!.find((a) => a.name === 'part.txt')!;
|
||||
expect(part.blobId).toBeTruthy();
|
||||
|
||||
const name = `${tag}-part.txt`;
|
||||
const first = await saveAttachmentToFiles({ blobId: part.blobId!, name, type: 'text/plain' }, null);
|
||||
createdNodes.push(first.id);
|
||||
const stored = await nodeBlob(first.id);
|
||||
expect(text(await singleton.fetchBlobArrayBuffer(stored.blobId, name, 'text/plain'))).toBe(content);
|
||||
|
||||
// The same name again: the server would refuse it (alreadyExists).
|
||||
const second = await saveAttachmentToFiles({ blobId: part.blobId!, name, type: 'text/plain' }, null);
|
||||
createdNodes.push(second.id);
|
||||
expect(second.name).toBe(`${tag}-part (1).txt`);
|
||||
|
||||
// And back: the file attached to a new draft carries the same bytes.
|
||||
const back = await attachmentFromFileNode({ ...stored, id: first.id, nodeType: 'file', type: 'text/plain' });
|
||||
const reply = await saveDraftVersion({
|
||||
from: [{ email: USER }], to: [{ email: USER }], subject: `back ${tag}`, textBody: 'see attached',
|
||||
attachments: [{ blobId: back.blobId, name: 'back.txt', type: 'text/plain', disposition: 'attachment' }],
|
||||
}, drafts);
|
||||
createdEmails.push(reply.id);
|
||||
const [again] = await getFullEmails([reply.id]);
|
||||
const attached = again.attachments!.find((a) => a.name === 'back.txt')!;
|
||||
expect(text(await singleton.fetchBlobArrayBuffer(attached.blobId!, 'back.txt', 'text/plain'))).toBe(content);
|
||||
}, 60_000);
|
||||
|
||||
it('copies blobs with Blob/copy only when the blob capability is in `using`', async () => {
|
||||
const upload = await uploadBytes(new TextEncoder().encode(`copy ${RUN}`), 'text/plain');
|
||||
const call = ['Blob/copy', { fromAccountId: singleton.accountId, accountId: singleton.accountId, blobIds: [upload.blobId] }, '0'];
|
||||
|
||||
@@ -426,6 +426,17 @@ function usesDraftFileNodeCreate(accountId: string): boolean {
|
||||
return !!cap && typeof cap === 'object' && 'supportedNodeTypes' in cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a FileNode made from a message part's blobId holds that part.
|
||||
* Stalwart 1.0.0 stores the part; 0.16 stores the whole raw message the part
|
||||
* is in under the part's name (seen on 0.16.27), so there the part's bytes
|
||||
* have to be uploaded as a blob of their own first. Told apart as for the
|
||||
* create shape: only 1.0.0 implements File Storage draft 18.
|
||||
*/
|
||||
export function fileNodeTakesPartBlobs(accountId: string = filesAccountId()): boolean {
|
||||
return usesDraftFileNodeCreate(accountId);
|
||||
}
|
||||
|
||||
/** One FileNode/set create in `accountId`, in the shape that server takes. */
|
||||
async function createFileNodeIn(
|
||||
accountId: string,
|
||||
|
||||
Reference in New Issue
Block a user