Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions internal/email/archera_signup_url_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package email

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// Issue #48: the email templates link to the same Archera signup URL as the
// CLI and the web frontend. The expected value is written out literally here
// on purpose rather than read from common.ArcheraSignupURL: if the shared
// constant is ever pointed at a wrong destination, these tests fail instead
// of following it.
const (
wantArcheraSignupURL = "https://www.archera.ai/cudly"
legacyArcheraSignupURL = "https://archera.ai/signup?mode=cudly"

// The two partnership disclosures that must stay visible wherever the
// signup link is shown, in the wording the email templates use.
archeraNonGatingDisclosure = "Optional, set up on Archera's site"
archeraSponsorshipDisclosure = "Archera sponsors CUDly's development"
)

func archeraEmailData() NotificationData {
return NotificationData{
DashboardURL: "https://dashboard.example.com",
ApprovalToken: "tkn-48",
ExecutionID: "exec-48",
TotalUpfrontCost: 500.00,
TotalSavings: 50.00,
ArcheraEducationURL: "https://dashboard.example.com/archera-insurance",
Recommendations: []RecommendationSummary{{
Service: "ec2", ResourceType: "m5.large", Region: "us-east-1",
Count: 1, MonthlySavings: 50.00,
}},
}
}

// Every template family that embeds the Archera signup link: purchase
// confirmation (text only) and purchase approval request (text and HTML).
var archeraSignupRenderers = []struct {
name string
render func(NotificationData) (string, error)
section string // heading that opens the Archera block in this template
link string // how the URL must appear in the rendered body
}{
{
name: "purchase confirmation (text)",
render: RenderPurchaseConfirmationEmail,
section: "ARCHERA INSURANCE",
link: "Sign up: " + wantArcheraSignupURL + "\n",
},
{
name: "purchase approval request (text)",
render: RenderPurchaseApprovalRequestEmail,
section: "ARCHERA INSURANCE",
link: "Sign up: " + wantArcheraSignupURL + "\n",
},
{
name: "purchase approval request (HTML)",
render: RenderPurchaseApprovalRequestEmailHTML,
section: ">Archera Insurance</h2>",
link: `href="` + wantArcheraSignupURL + `"`,
},
}

func TestArcheraSignupURL_EmailTemplatesUseCanonicalURL(t *testing.T) {
for _, tc := range archeraSignupRenderers {
t.Run(tc.name, func(t *testing.T) {
body, err := tc.render(archeraEmailData())
require.NoError(t, err)

assert.Contains(t, body, tc.link)
assert.NotContains(t, body, legacyArcheraSignupURL)
assert.NotContains(t, body, "archera.ai/signup")
})
}
}

// The signup link must never appear without both disclosures in the same
// Archera block.
func TestArcheraSignupURL_DisclosuresStayNextToLink(t *testing.T) {
for _, tc := range archeraSignupRenderers {
t.Run(tc.name, func(t *testing.T) {
body, err := tc.render(archeraEmailData())
require.NoError(t, err)

start := strings.Index(body, tc.section)
require.GreaterOrEqual(t, start, 0, "Archera block heading %q not found", tc.section)
block := body[start:]

assert.Contains(t, block, wantArcheraSignupURL)
assert.Contains(t, block, archeraNonGatingDisclosure)
assert.Contains(t, block, archeraSponsorshipDisclosure)
})
}
}

// Without an education URL the whole Archera block, signup link included, is
// left out.
func TestArcheraSignupURL_OmittedWithoutEducationURL(t *testing.T) {
for _, tc := range archeraSignupRenderers {
t.Run(tc.name, func(t *testing.T) {
data := archeraEmailData()
data.ArcheraEducationURL = ""
body, err := tc.render(data)
require.NoError(t, err)

assert.NotContains(t, body, wantArcheraSignupURL)
})
}
}

// No email template source in this package may still carry the old link,
// including templates added later that the renderers above do not cover.
func TestArcheraSignupURL_NoTemplateSourceHasLegacyURL(t *testing.T) {
files, err := filepath.Glob("*.go")
require.NoError(t, err)
require.NotEmpty(t, files)

for _, f := range files {
if strings.HasSuffix(f, "_test.go") {
continue
}
src, err := os.ReadFile(f) //nolint:gosec // fixed glob over this package's own source files
require.NoError(t, err)
assert.NotContains(t, string(src), legacyArcheraSignupURL, "%s still contains the legacy Archera signup URL", f)
assert.NotContains(t, string(src), "archera.ai/signup", "%s still contains an archera.ai/signup link", f)
}
}
2 changes: 1 addition & 1 deletion internal/email/template_renderers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ func TestRenderPurchaseConfirmationEmail_ArcheraBlock(t *testing.T) {

assert.Contains(t, body, "Archera")
assert.Contains(t, body, "7-day") // confirmation template uses "7-day enrollment window"
assert.Contains(t, body, "https://archera.ai/signup?mode=cudly")
assert.Contains(t, body, "https://www.archera.ai/cudly")
assert.Contains(t, body, "https://dashboard.example.com/archera-insurance")
}

Expand Down
11 changes: 8 additions & 3 deletions internal/email/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import (
)

// Email templates
//
// The Archera signup link in the purchase confirmation and approval request
// templates is spliced in from common.ArcheraSignupURL, the constant the CLI
// also uses, so every surface sends buyers to the same attribution URL. The
// frontend keeps its own copy in frontend/src/archera.ts (ARCHERA_SIGNUP_URL).

const newRecommendationsTemplate = `CUDly - New Commitment Recommendations Available
================================================
Expand Down Expand Up @@ -119,7 +124,7 @@ terms, coverage, and pricing are set entirely by Archera. CUDly has no
visibility into your Archera account or policy. Review Archera's terms
of service and privacy policy before signing up.

Sign up: https://archera.ai/signup?mode=cudly
Sign up: ` + common.ArcheraSignupURL + `
Permalink: {{.ArcheraEducationURL}}
{{end}}
This is an automated message from CUDly.
Expand Down Expand Up @@ -660,7 +665,7 @@ useful, but you should know about the financial relationship. Insurance
terms, coverage, and pricing are set entirely by Archera. CUDly has no
visibility into the buyer's Archera account or policy.

Sign up: https://archera.ai/signup?mode=cudly
Sign up: ` + common.ArcheraSignupURL + `
Permalink: {{.ArcheraEducationURL}}
{{end}}
This is an automated message from CUDly. Do not share these links.
Expand Down Expand Up @@ -758,7 +763,7 @@ const purchaseApprovalRequestHTMLTemplate = `<!DOCTYPE html>
</ol>
<p style="margin:6px 0;">Archera charges an insurance premium for the coverage selected, a separate fee paid to Archera. The cloud commitment purchased through CUDly is unaffected: same price, same billing.</p>

<p style="margin:12px 0 4px;"><a href="https://archera.ai/signup?mode=cudly" style="display:inline-block;padding:10px 22px;background:#1a73e8;color:#ffffff;text-decoration:none;font-weight:600;font-size:13px;border-radius:6px;">Sign up at Archera &rarr;</a></p>
<p style="margin:12px 0 4px;"><a href="` + common.ArcheraSignupURL + `" style="display:inline-block;padding:10px 22px;background:#1a73e8;color:#ffffff;text-decoration:none;font-weight:600;font-size:13px;border-radius:6px;">Sign up at Archera &rarr;</a></p>

<p style="color:#64748b;font-size:12px;font-style:italic;border-top:1px dashed #e2e8f0;padding-top:10px;margin-top:12px;"><strong>Full disclosure:</strong> Archera sponsors CUDly's development with a share of their insurance revenue; we surface the option because we think it's useful, but you should know about the financial relationship. Insurance terms, coverage, and pricing are set entirely by Archera. CUDly has no visibility into the buyer's Archera account or policy.</p>

Expand Down
Loading