diff --git a/Sources/FreeAgentCLI/Commands/Invoice/InvoiceSendEmailCommand.swift b/Sources/FreeAgentCLI/Commands/Invoice/InvoiceSendEmailCommand.swift index c5e8985..3fedcf2 100644 --- a/Sources/FreeAgentCLI/Commands/Invoice/InvoiceSendEmailCommand.swift +++ b/Sources/FreeAgentCLI/Commands/Invoice/InvoiceSendEmailCommand.swift @@ -23,15 +23,31 @@ struct InvoiceSendEmailCommand: MutatingCommand { @Option(name: .long, help: "Email subject") var subject: String? + @Option( + name: .customLong("attachment"), + help: "Path to a file to attach (pdf, png, jpg, jpeg or gif), up to 5MB. Repeat for multiple files." + ) + var attachments = [String]() + @Flag(help: "Print the request instead of sending it") var dryRun = false func perform(client: Client) async throws -> EmptyResponse { + let attachmentPayloads = try attachments.map { path in + let file = try AttachmentFile(path: path) + return Components.Schemas.EmailAttachmentPayload( + data: file.data, + fileName: file.fileName, + contentType: file.contentType + ) + } + let emailPayload = Components.Schemas.EmailPayload( body: body, from: from, subject: subject, - to: to + to: to, + attachments: attachmentPayloads.isEmpty ? nil : attachmentPayloads ) let invoicePayload = Operations.SendInvoiceEmail.Input.Body.JsonPayload.InvoicePayload( diff --git a/Tests/FreeAgentCLITests/Commands/Invoice/InvoiceSendEmailCommandTests.swift b/Tests/FreeAgentCLITests/Commands/Invoice/InvoiceSendEmailCommandTests.swift new file mode 100644 index 0000000..1d934f4 --- /dev/null +++ b/Tests/FreeAgentCLITests/Commands/Invoice/InvoiceSendEmailCommandTests.swift @@ -0,0 +1,24 @@ +import Foundation +import FreeAgentAPI +import Mockable +import Testing + +@testable import FreeAgentCLI + +struct InvoiceSendEmailCommandTests { + @Test("refuses an unsupported attachment without sending the email") + func unsupportedAttachment() async throws { + let path = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).txt").path + try Data("timesheet".utf8).write(to: URL(fileURLWithPath: path)) + + let transport = MockClientTransportInterface() + let client = Client(serverURL: Environment.sandbox.baseURL, transport: transport) + let command = try InvoiceSendEmailCommand.parse(["1234", "--to", "alice@example.com", "--attachment", path]) + + await #expect(throws: AttachmentFileError.self) { + try await command.perform(client: client) + } + + verify(transport).send(.any, body: .any, baseURL: .any, operationID: .value("sendInvoiceEmail")).called(0) + } +} diff --git a/openapi/components/schemas/EmailAttachmentPayload.yaml b/openapi/components/schemas/EmailAttachmentPayload.yaml new file mode 100644 index 0000000..983fc31 --- /dev/null +++ b/openapi/components/schemas/EmailAttachmentPayload.yaml @@ -0,0 +1,17 @@ +type: object +required: +- data +- file_name +- content_type +properties: + data: + type: string + description: Binary data of the file encoded as base64 + example: iVBORw0KGgo... + file_name: + type: string + example: timesheet.pdf + content_type: + type: string + description: Required, but ignored - FreeAgent detects the type from the data + example: application/pdf diff --git a/openapi/components/schemas/EmailPayload.yaml b/openapi/components/schemas/EmailPayload.yaml index de5015d..037ce63 100644 --- a/openapi/components/schemas/EmailPayload.yaml +++ b/openapi/components/schemas/EmailPayload.yaml @@ -11,4 +11,9 @@ properties: example: Testing to: type: string - example: Test Contact + example: test_contact@example.com + attachments: + type: array + description: Files of up to 5MB each + items: + $ref: '#/components/schemas/EmailAttachmentPayload' diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 6043c54..6b7293c 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -479,6 +479,8 @@ components: $ref: 'components/schemas/InvoiceUpdatePayload.yaml' EmailPayload: $ref: 'components/schemas/EmailPayload.yaml' + EmailAttachmentPayload: + $ref: 'components/schemas/EmailAttachmentPayload.yaml' TokenRequest: $ref: 'components/schemas/TokenRequest.yaml' AuthorizationCodeGrant: diff --git a/openapi/paths/credit_notes.yaml b/openapi/paths/credit_notes.yaml index d27248d..312a350 100644 --- a/openapi/paths/credit_notes.yaml +++ b/openapi/paths/credit_notes.yaml @@ -223,7 +223,7 @@ send_email: body: Please find our credit note attached from: Our Company subject: Credit Note - to: Test Contact + to: test_contact@example.com responses: '200': description: Success diff --git a/openapi/paths/estimates.yaml b/openapi/paths/estimates.yaml index 1344ae0..ddabadc 100644 --- a/openapi/paths/estimates.yaml +++ b/openapi/paths/estimates.yaml @@ -214,7 +214,7 @@ send_email: body: Please find our estimate attached from: Our Company subject: Estimate - to: Test Contact + to: test_contact@example.com responses: '200': description: Success diff --git a/openapi/paths/invoices.yaml b/openapi/paths/invoices.yaml index f1b5efe..aa71cd0 100644 --- a/openapi/paths/invoices.yaml +++ b/openapi/paths/invoices.yaml @@ -306,7 +306,7 @@ send_email: body: Bison bison bison bison bison Bison bison from: Our Company subject: Testing - to: Test Contact + to: test_contact@example.com responses: '200': description: Success