Skip to content

Commit 89a6068

Browse files
committed
Fix CI reliability and stabilize macOS GPG integration tests
1 parent adff55a commit 89a6068

12 files changed

Lines changed: 133 additions & 49 deletions

File tree

.golangci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ linters:
1414
# Bugs
1515
- durationcheck
1616
- reassign
17-
disable:
18-
- staticcheck
1917

2018
settings:
2119
govet:

Makefile

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,13 @@ test-air-integration:
125125
# -p 1: Run test packages sequentially to prevent rate limit issues
126126
test-integration:
127127
@go clean -testcache
128-
NYLAS_DISABLE_KEYRING=true \
129-
NYLAS_TEST_RATE_LIMIT_RPS=$(NYLAS_TEST_RATE_LIMIT_RPS) \
130-
NYLAS_TEST_RATE_LIMIT_BURST=$(NYLAS_TEST_RATE_LIMIT_BURST) \
131-
NYLAS_TEST_BINARY=$(CURDIR)/bin/nylas \
132-
go test ./... -tags=integration -v -timeout 10m -p 1 2>&1 | tee test-integration.txt
128+
@bash -o pipefail -c '\
129+
NYLAS_DISABLE_KEYRING=true \
130+
NYLAS_TEST_RATE_LIMIT_RPS=$(NYLAS_TEST_RATE_LIMIT_RPS) \
131+
NYLAS_TEST_RATE_LIMIT_BURST=$(NYLAS_TEST_RATE_LIMIT_BURST) \
132+
NYLAS_TEST_BINARY=$(CURDIR)/bin/nylas \
133+
go test ./... -tags=integration -v -timeout 10m -p 1 2>&1 | tee test-integration.txt \
134+
'
133135

134136
# Integration tests excluding slow LLM-dependent tests (for when Ollama is slow/unavailable)
135137
# Runs: Admin, Timezone, AIConfig, CalendarAI (Basic, Adapt, Analyze working hours)
@@ -302,25 +304,30 @@ ci-full:
302304
@echo "================================="
303305
@echo "Running Full CI Pipeline..."
304306
@echo "================================="
305-
@$(MAKE) --no-print-directory ci 2>&1 | tee ci-full.txt
306-
@echo "" | tee ci-full.txt
307-
@echo "=================================" | tee ci-full.txt
308-
@echo "Running Integration Tests..." | tee ci-full.txt
309-
@echo "=================================" | tee ci-full.txt
310-
@$(MAKE) --no-print-directory test-integration 2>&1 | tee ci-full.txt
311-
@$(MAKE) --no-print-directory test-air-integration 2>&1 | tee ci-full.txt
312-
@echo "" | tee ci-full.txt
313-
@echo "=================================" | tee ci-full.txt
314-
@echo "Cleaning up test resources..." | tee ci-full.txt
315-
@echo "=================================" | tee ci-full.txt
316-
@$(MAKE) --no-print-directory test-cleanup 2>&1 | tee ci-full.txt
317-
@echo "" | tee ci-full.txt
318-
@echo "=================================" | tee ci-full.txt
319-
@echo "✓ Full CI pipeline completed!" | tee ci-full.txt
320-
@echo " - All quality checks passed" | tee ci-full.txt
321-
@echo " - All tests passed" | tee ci-full.txt
322-
@echo " - Test resources cleaned up" | tee ci-full.txt
323-
@echo "=================================" | tee ci-full.txt
307+
@: > ci-full.txt
308+
@bash -o pipefail -c '\
309+
set -eu; \
310+
exec > >(tee -a ci-full.txt) 2>&1; \
311+
$(MAKE) --no-print-directory ci; \
312+
echo ""; \
313+
echo "================================="; \
314+
echo "Running Integration Tests..."; \
315+
echo "================================="; \
316+
$(MAKE) --no-print-directory test-integration; \
317+
$(MAKE) --no-print-directory test-air-integration; \
318+
echo ""; \
319+
echo "================================="; \
320+
echo "Cleaning up test resources..."; \
321+
echo "================================="; \
322+
$(MAKE) --no-print-directory test-cleanup; \
323+
echo ""; \
324+
echo "================================="; \
325+
echo "✓ Full CI pipeline completed!"; \
326+
echo " - All quality checks passed"; \
327+
echo " - All tests passed"; \
328+
echo " - Test resources cleaned up"; \
329+
echo "================================="; \
330+
'
324331
@echo ""
325332
@echo "Results saved to ci-full.txt"
326333

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/nylas/cli
22

3-
go 1.24.2
3+
go 1.26.0
44

55
require (
66
github.com/atotto/clipboard v0.1.4

internal/adapters/ai/email_analyzer_core.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,17 @@ func (a *EmailAnalyzer) fetchThreadMessages(ctx context.Context, grantID, thread
8080
func (a *EmailAnalyzer) buildThreadContext(thread *domain.Thread, messages []domain.Message) string {
8181
var builder strings.Builder
8282

83-
builder.WriteString(fmt.Sprintf("Email Thread: %s\n", thread.Subject))
84-
builder.WriteString(fmt.Sprintf("Participants: %d\n", len(thread.Participants)))
85-
builder.WriteString(fmt.Sprintf("Messages: %d\n\n", len(messages)))
83+
_, _ = fmt.Fprintf(&builder, "Email Thread: %s\n", thread.Subject)
84+
_, _ = fmt.Fprintf(&builder, "Participants: %d\n", len(thread.Participants))
85+
_, _ = fmt.Fprintf(&builder, "Messages: %d\n\n", len(messages))
8686

8787
// Add participants
8888
builder.WriteString("Participants:\n")
8989
for _, p := range thread.Participants {
9090
if p.Name != "" {
91-
builder.WriteString(fmt.Sprintf("- %s <%s>\n", p.Name, p.Email))
91+
_, _ = fmt.Fprintf(&builder, "- %s <%s>\n", p.Name, p.Email)
9292
} else {
93-
builder.WriteString(fmt.Sprintf("- %s\n", p.Email))
93+
_, _ = fmt.Fprintf(&builder, "- %s\n", p.Email)
9494
}
9595
}
9696
builder.WriteString("\n")
@@ -111,7 +111,7 @@ func (a *EmailAnalyzer) buildThreadContext(thread *domain.Thread, messages []dom
111111
// Format timestamp
112112
timestamp := msg.Date.Format("Jan 2, 2006 3:04 PM")
113113

114-
builder.WriteString(fmt.Sprintf("\n[%s] %s:\n", timestamp, sender))
114+
_, _ = fmt.Fprintf(&builder, "\n[%s] %s:\n", timestamp, sender)
115115

116116
// Add message body (truncate if too long)
117117
body := msg.Body

internal/adapters/ai/email_analyzer_prompts.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,17 +314,17 @@ func (a *EmailAnalyzer) AnalyzeInbox(ctx context.Context, req *InboxSummaryReque
314314
func (a *EmailAnalyzer) buildInboxPrompt(messages []domain.Message) string {
315315
var sb strings.Builder
316316

317-
sb.WriteString(fmt.Sprintf("Analyze these %d emails and provide insights:\n\n", len(messages)))
317+
_, _ = fmt.Fprintf(&sb, "Analyze these %d emails and provide insights:\n\n", len(messages))
318318

319319
for i, msg := range messages {
320-
sb.WriteString(fmt.Sprintf("--- Email %d ---\n", i+1))
321-
sb.WriteString(fmt.Sprintf("From: %s\n", formatInboxParticipants(msg.From)))
322-
sb.WriteString(fmt.Sprintf("Subject: %s\n", msg.Subject))
323-
sb.WriteString(fmt.Sprintf("Date: %s\n", msg.Date.Format(time.RFC3339)))
320+
_, _ = fmt.Fprintf(&sb, "--- Email %d ---\n", i+1)
321+
_, _ = fmt.Fprintf(&sb, "From: %s\n", formatInboxParticipants(msg.From))
322+
_, _ = fmt.Fprintf(&sb, "Subject: %s\n", msg.Subject)
323+
_, _ = fmt.Fprintf(&sb, "Date: %s\n", msg.Date.Format(time.RFC3339))
324324

325325
// Use snippet for preview (cleaner than full body)
326326
if msg.Snippet != "" {
327-
sb.WriteString(fmt.Sprintf("Preview: %s\n", truncateStr(msg.Snippet, 200)))
327+
_, _ = fmt.Fprintf(&sb, "Preview: %s\n", truncateStr(msg.Snippet, 200))
328328
}
329329

330330
if msg.Unread {

internal/adapters/config/validation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func FormatMissingEnvVars(missing []string, vars []RequiredEnvVar) string {
5252
if desc == "" {
5353
desc = "No description available"
5454
}
55-
sb.WriteString(fmt.Sprintf(" %s - %s\n", name, desc))
55+
_, _ = fmt.Fprintf(&sb, " %s - %s\n", name, desc)
5656
}
5757

5858
return sb.String()

internal/adapters/gpg/decrypt_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ func TestDecryptData_Integration(t *testing.T) {
150150
// Decrypt the data
151151
decResult, err := svc.DecryptData(ctx, encResult.Ciphertext)
152152
if err != nil {
153+
if isNonInteractiveGPGError(err.Error()) {
154+
t.Skipf("GPG requires interactive passphrase entry in this environment: %v", err)
155+
}
153156
t.Fatalf("DecryptData() error = %v", err)
154157
}
155158

@@ -196,6 +199,9 @@ func TestDecryptSignedAndEncryptedData_Integration(t *testing.T) {
196199
// Decrypt the data
197200
decResult, err := svc.DecryptData(ctx, encResult.Ciphertext)
198201
if err != nil {
202+
if isNonInteractiveGPGError(err.Error()) {
203+
t.Skipf("GPG requires interactive passphrase entry in this environment: %v", err)
204+
}
199205
t.Fatalf("DecryptData() error = %v", err)
200206
}
201207

@@ -214,3 +220,23 @@ func TestDecryptSignedAndEncryptedData_Integration(t *testing.T) {
214220
t.Error("Expected SignatureOK=true for valid signature")
215221
}
216222
}
223+
224+
func isNonInteractiveGPGError(errMsg string) bool {
225+
nonInteractiveErrors := []string{
226+
"cannot open '/dev/tty'",
227+
"no pinentry",
228+
"inappropriate ioctl for device",
229+
"need_passphrase",
230+
"inquire_maxlen",
231+
"operation cancelled",
232+
}
233+
234+
lowerErr := strings.ToLower(errMsg)
235+
for _, marker := range nonInteractiveErrors {
236+
if strings.Contains(lowerErr, marker) {
237+
return true
238+
}
239+
}
240+
241+
return false
242+
}

internal/adapters/mime/builder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func (b *builder) BuildSignedMessage(req *SignedMessageRequest) ([]byte, error)
111111

112112
// Write Content-Type for multipart/signed
113113
buf.WriteString("Content-Type: multipart/signed; protocol=\"application/pgp-signature\";\r\n")
114-
buf.WriteString(fmt.Sprintf("\tmicalg=%s; boundary=\"%s\"\r\n", micalg, signedBoundary))
114+
_, _ = fmt.Fprintf(&buf, "\tmicalg=%s; boundary=\"%s\"\r\n", micalg, signedBoundary)
115115
buf.WriteString("\r\n")
116116

117117
// Write first part: the content to be signed

internal/adapters/mime/encrypted.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (b *builder) BuildEncryptedMessage(req *EncryptedMessageRequest) ([]byte, e
7575
// Write Content-Type for multipart/encrypted (RFC 3156 Section 4)
7676
buf.WriteString("Content-Type: multipart/encrypted;\r\n")
7777
buf.WriteString("\tprotocol=\"application/pgp-encrypted\";\r\n")
78-
buf.WriteString(fmt.Sprintf("\tboundary=\"%s\"\r\n", encryptedBoundary))
78+
_, _ = fmt.Fprintf(&buf, "\tboundary=\"%s\"\r\n", encryptedBoundary)
7979
buf.WriteString("\r\n")
8080

8181
// Part 1: Version identification (required by RFC 3156)

internal/air/handlers_ai_thread.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ func (s *Server) handleAIThreadSummary(w http.ResponseWriter, r *http.Request) {
5151
body = body[:1000] + "..."
5252
}
5353

54-
conversationBuilder.WriteString(fmt.Sprintf("--- Message %d ---\n", i+1))
55-
conversationBuilder.WriteString(fmt.Sprintf("From: %s\n", msg.From))
54+
_, _ = fmt.Fprintf(&conversationBuilder, "--- Message %d ---\n", i+1)
55+
_, _ = fmt.Fprintf(&conversationBuilder, "From: %s\n", msg.From)
5656
if msg.Subject != "" {
57-
conversationBuilder.WriteString(fmt.Sprintf("Subject: %s\n", msg.Subject))
57+
_, _ = fmt.Fprintf(&conversationBuilder, "Subject: %s\n", msg.Subject)
5858
}
5959
conversationBuilder.WriteString(body)
6060
conversationBuilder.WriteString("\n\n")

0 commit comments

Comments
 (0)