mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-17 05:07:23 +08:00
### Summary As title <img width="3761" height="2036" alt="image" src="https://github.com/user-attachments/assets/775f8c2a-784e-4713-bcf0-c26bfc368dac" /> <img width="1256" height="2760" alt="d2aeeb50dd11cc8dc76bc4743425680e" src="https://github.com/user-attachments/assets/80f9c572-549d-427f-8e48-35dc04cfa98a" />
224 lines
5.9 KiB
Go
224 lines
5.9 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
package component
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"ragflow/internal/agent/runtime"
|
|
)
|
|
|
|
func TestEmailComponentRegisteredAndSends(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("Listen: %v", err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
var receivedData strings.Builder
|
|
done := make(chan struct{})
|
|
go runEmailMockSMTP(t, ln, &receivedData, done)
|
|
|
|
_, port, err := net.SplitHostPort(ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("SplitHostPort: %v", err)
|
|
}
|
|
var portInt int
|
|
_, _ = fmt.Sscanf(port, "%d", &portInt)
|
|
|
|
c, err := New(componentNameEmail, map[string]any{
|
|
"smtp_server": "127.0.0.1",
|
|
"smtp_port": portInt,
|
|
"email": "alice@example.com",
|
|
"smtp_username": "",
|
|
"password": "",
|
|
"sender_name": "Alice",
|
|
"to_email": "bob@example.com",
|
|
"subject": "Build status",
|
|
"content": "Build succeeded.",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New Email: %v", err)
|
|
}
|
|
|
|
state := runtime.NewCanvasState("run-email", "task-email")
|
|
state.Sys["date"] = "2026-07-14 03:04:05"
|
|
ctx := runtime.WithState(context.Background(), state)
|
|
|
|
out, err := c.Invoke(ctx, nil)
|
|
if err != nil {
|
|
t.Fatalf("Invoke: %v", err)
|
|
}
|
|
if out["success"] != true {
|
|
t.Fatalf("success = %v, want true; out=%v", out["success"], out)
|
|
}
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("mock SMTP server did not close in time")
|
|
}
|
|
|
|
data := receivedData.String()
|
|
for _, want := range []string{"Subject: Build status", "bob@example.com", "Build succeeded."} {
|
|
if !strings.Contains(data, want) {
|
|
t.Fatalf("mock SMTP payload missing %q\n--- data ---\n%s\n---", want, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEmailComponentResolvesSysDateInSubject(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("Listen: %v", err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
var receivedData strings.Builder
|
|
done := make(chan struct{})
|
|
go runEmailMockSMTP(t, ln, &receivedData, done)
|
|
|
|
_, port, err := net.SplitHostPort(ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("SplitHostPort: %v", err)
|
|
}
|
|
var portInt int
|
|
_, _ = fmt.Sscanf(port, "%d", &portInt)
|
|
|
|
c, err := New(componentNameEmail, map[string]any{
|
|
"smtp_server": "127.0.0.1",
|
|
"smtp_port": portInt,
|
|
"email": "alice@example.com",
|
|
"to_email": "bob@example.com",
|
|
"subject": "[noreply]{sys.date}",
|
|
"content": "body",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New Email: %v", err)
|
|
}
|
|
|
|
state := runtime.NewCanvasState("run-email", "task-email")
|
|
state.Sys["date"] = "2026-07-14 03:04:05"
|
|
out, err := c.Invoke(runtime.WithState(context.Background(), state), nil)
|
|
if err != nil {
|
|
t.Fatalf("Invoke: %v", err)
|
|
}
|
|
if out["success"] != true {
|
|
t.Fatalf("success = %v, want true; out=%v", out["success"], out)
|
|
}
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("mock SMTP server did not close in time")
|
|
}
|
|
|
|
if strings.Contains(receivedData.String(), "{sys.date}") {
|
|
t.Fatalf("subject still contains unresolved sys.date\n--- data ---\n%s\n---", receivedData.String())
|
|
}
|
|
if !strings.Contains(receivedData.String(), "Subject: [noreply]2026-07-14 03:04:05") {
|
|
t.Fatalf("subject did not resolve sys.date\n--- data ---\n%s\n---", receivedData.String())
|
|
}
|
|
}
|
|
|
|
func TestEmailComponentSoftFails(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c, err := New(componentNameEmail, map[string]any{
|
|
"smtp_port": 465,
|
|
"email": "alice@example.com",
|
|
"to_email": "bob@example.com",
|
|
"subject": "Subject",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New Email: %v", err)
|
|
}
|
|
out, err := c.Invoke(context.Background(), nil)
|
|
if err != nil {
|
|
t.Fatalf("Invoke returned hard error: %v", err)
|
|
}
|
|
if out["success"] != false {
|
|
t.Fatalf("success = %v, want false; out=%v", out["success"], out)
|
|
}
|
|
if _, ok := out["_ERROR"].(string); !ok {
|
|
t.Fatalf("_ERROR missing or not string: %v", out)
|
|
}
|
|
}
|
|
|
|
func runEmailMockSMTP(t *testing.T, ln net.Listener, receivedData *strings.Builder, done chan<- struct{}) {
|
|
t.Helper()
|
|
defer close(done)
|
|
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
reader := bufio.NewReader(conn)
|
|
writer := bufio.NewWriter(conn)
|
|
_, _ = writer.WriteString("220 mock-smtp ready\r\n")
|
|
_ = writer.Flush()
|
|
|
|
inData := false
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return
|
|
}
|
|
up := strings.ToUpper(strings.TrimSpace(line))
|
|
switch {
|
|
case strings.HasPrefix(up, "EHLO"), strings.HasPrefix(up, "HELO"):
|
|
_, _ = writer.WriteString("250-mock-smtp\r\n250-AUTH PLAIN\r\n250 OK\r\n")
|
|
_ = writer.Flush()
|
|
case strings.HasPrefix(up, "AUTH PLAIN"):
|
|
_, _ = writer.WriteString("235 Authentication successful\r\n")
|
|
_ = writer.Flush()
|
|
case strings.HasPrefix(up, "MAIL FROM:"), strings.HasPrefix(up, "RCPT TO:"):
|
|
_, _ = writer.WriteString("250 OK\r\n")
|
|
_ = writer.Flush()
|
|
case strings.HasPrefix(up, "DATA"):
|
|
_, _ = writer.WriteString("354 End data with <CR><LF>.<CR><LF>\r\n")
|
|
_ = writer.Flush()
|
|
inData = true
|
|
case inData && strings.TrimSpace(line) == ".":
|
|
_, _ = writer.WriteString("250 Queued\r\n")
|
|
_ = writer.Flush()
|
|
inData = false
|
|
case inData:
|
|
receivedData.WriteString(line)
|
|
case strings.HasPrefix(up, "QUIT"):
|
|
_, _ = writer.WriteString("221 Bye\r\n")
|
|
_ = writer.Flush()
|
|
return
|
|
default:
|
|
_, _ = writer.WriteString("250 OK\r\n")
|
|
_ = writer.Flush()
|
|
}
|
|
}
|
|
}
|