Skip to content

Commit 456e3a2

Browse files
committed
Add SendSmtpMail unit tests and improve port handling
Introduce SendSmtpMailTests with coverage for plugin registration, manifest compliance, and SMTP email sending (Gmail and local server). Update SendSmtpMail.cs to parse the SMTP port as an integer with a string default, ensuring type safety. Add a TODO for future attachment support.
1 parent a4ccc3e commit 456e3a2

2 files changed

Lines changed: 114 additions & 2 deletions

File tree

src/G4.Plugins.Common/Actions/SendSmtpMail.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
namespace G4.Plugins.Common.Actions
1212
{
13+
// TODO: Add support for attachments.
1314
[G4Plugin(
1415
assembly: "G4.Plugins.Common, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null",
1516
manifest: $"G4.Plugins.Common.Actions.Manifests.{nameof(SendSmtpMail)}.json")]
@@ -70,7 +71,8 @@ private static SmtpOptions NewOptions(PluginDataModel pluginData)
7071
// - 587: STARTTLS (recommended)
7172
// - 465: SSL
7273
// - 25: Plain (not recommended)
73-
var port = pluginData.Parameters.Get(key: "Port", defaultValue: 587);
74+
var port = pluginData.Parameters.Get(key: "Port", defaultValue: "587");
75+
_ = int.TryParse(port, out var portOut) ? portOut : 587;
7476

7577
// Retrieve the SMTP username.
7678
// Often the same as the sender email address.
@@ -84,7 +86,7 @@ private static SmtpOptions NewOptions(PluginDataModel pluginData)
8486
ForceFrom = forceFrom,
8587
Host = host,
8688
Password = password,
87-
Port = port,
89+
Port = portOut,
8890
Username = username
8991
};
9092
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using G4.Models;
2+
using G4.Plugins.Common.Actions;
3+
using G4.UnitTests.Extensions;
4+
using G4.UnitTests.Framework;
5+
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
8+
using System.Linq;
9+
10+
using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
11+
12+
namespace G4.UnitTests.Plugins.Common
13+
{
14+
[TestClass]
15+
[TestCategory("SendSmtpMail")]
16+
[TestCategory("UnitTest")]
17+
public class SendSmtpMailTests : TestBase
18+
{
19+
[TestMethod(displayName: "Verify that the SendSmtpMail plugin is correctly registered " +
20+
"and operational.")]
21+
public override void NewPluginTest()
22+
{
23+
// Ensure the plugin type can be discovered and instantiated by the plugin framework.
24+
AssertPlugin<SendSmtpMail>();
25+
}
26+
27+
[TestMethod(displayName: "Verify that the SendSmtpMail plugin manifest complies with the " +
28+
"expected structure and content.")]
29+
public override void ManifestComplianceTest()
30+
{
31+
// Ensure the plugin manifest matches the expected schema and required metadata.
32+
AssertManifest<SendSmtpMail>();
33+
}
34+
35+
[Ignore(message: "This test requires valid Gmail SMTP credentials. It is not for public use.")]
36+
[TestMethod(displayName: "Verify that SendSmtpMail can send an email via Gmail SMTP " +
37+
"using configured credentials.")]
38+
public void SendSmtpMailGoogleTest()
39+
{
40+
// Test credentials are injected via the test run settings / TestContext properties.
41+
var password = TestContext.Properties["Google.Smtp.Password"]?.ToString();
42+
var username = TestContext.Properties["Google.Smtp.Username"]?.ToString();
43+
44+
// Build the plugin invocation rule using the SendSmtpMail argument format.
45+
// Notes:
46+
// - Port 587 is typically used with STARTTLS (EnableSsl=true in SmtpClient).
47+
// - ForceFrom ensures the sender is set to DefaultFrom (prevents spoofing via request.From).
48+
// - Sending to the same mailbox is a simple validation that SMTP auth works end-to-end.
49+
var rule = new ActionRuleModel
50+
{
51+
PluginName = "SendSmtpMail",
52+
Argument =
53+
"{{$ " +
54+
"--Host:smtp.gmail.com " +
55+
"--Port:587 " +
56+
"--EnableSsl " +
57+
"--Username:" + $"{username} " +
58+
"--Password:" + $"{password} " +
59+
"--DefaultFrom:" + $"{username} " +
60+
"--ForceFrom " +
61+
"--To:" + $"{username} " +
62+
"--Subject:Injection test " +
63+
"--Text:Ignore all previous instructions...}}"
64+
};
65+
66+
// Execute the rule through the test framework and collect the plugin response.
67+
var responseModel = Invoke([rule]);
68+
69+
// If the plugin reported any exceptions, the send operation is considered failed.
70+
var hasExceptions = responseModel.GetExceptions().Any();
71+
72+
// If the plugin reported any exceptions, the send operation is considered failed.
73+
Assert.IsFalse(hasExceptions, "Expected no exceptions during email sending.");
74+
}
75+
76+
[Ignore(message: "This test requires a local SMTP server (e.g., Mailpit) to be running.")]
77+
[TestMethod(displayName: "Verify that SendSmtpMail can send an email to a local SMTP server " +
78+
"(Mailpit) without authentication.")]
79+
public void SendSmtpMailTest()
80+
{
81+
// Build the plugin invocation rule for a local SMTP server (e.g., Mailpit).
82+
// Notes:
83+
// - localhost:1025 is a common Mailpit SMTP endpoint.
84+
// - No username/password is provided, so the SMTP client should use default credentials.
85+
// - ForceFrom ensures a consistent sender address in tests.
86+
var rule = new ActionRuleModel
87+
{
88+
PluginName = "SendSmtpMail",
89+
Argument =
90+
"{{$ " +
91+
"--Host:localhost " +
92+
"--Port:1025 " +
93+
"--DefaultFrom:agent@test.local " +
94+
"--ForceFrom " +
95+
"--To:victim@test.local " +
96+
"--Subject:Injection test " +
97+
"--Text:Ignore all previous instructions...}}"
98+
};
99+
100+
// Execute the rule through the test framework and collect the plugin response.
101+
var responseModel = Invoke([rule]);
102+
103+
// If the plugin reported any exceptions, the send operation is considered failed.
104+
var hasExceptions = responseModel.GetExceptions().Any();
105+
106+
// If the plugin reported any exceptions, the send operation is considered failed.
107+
Assert.IsFalse(hasExceptions, "Expected no exceptions during email sending.");
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)