Add API Server (Go/PocketBase), Web App (Go BFF + Vue), Fly App (Flutter/DJI MSDK), Adobe Plugin, and Docker/Docker AIO deployment configs. Design assets and build artifacts are gitignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
2.6 KiB
Go
98 lines
2.6 KiB
Go
package filetransfer
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"pilotvault/apiserver/internal/plugins"
|
|
)
|
|
|
|
func TestDescriptor(t *testing.T) {
|
|
p := &Plugin{}
|
|
d := p.Descriptor()
|
|
if d.Name != "filetransfer" {
|
|
t.Fatalf("name = %q, want filetransfer", d.Name)
|
|
}
|
|
if d.Kind != plugins.KindBuiltin {
|
|
t.Fatalf("kind = %q, want builtin", d.Kind)
|
|
}
|
|
if len(d.Capabilities) == 0 {
|
|
t.Fatal("expected capabilities")
|
|
}
|
|
// Every secret field must be flagged so the manager masks it.
|
|
for _, f := range d.ConfigFields {
|
|
if f.Key == "password" || f.Key == "privateKey" || f.Key == "keyPassphrase" {
|
|
if !f.Secret {
|
|
t.Errorf("config field %q must be Secret", f.Key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInitDefaults(t *testing.T) {
|
|
p := &Plugin{}
|
|
if err := p.Init(context.Background(), map[string]string{"host": "h", "username": "u"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if p.protocol != protoSFTP {
|
|
t.Errorf("default protocol = %q, want sftp", p.protocol)
|
|
}
|
|
if p.effectivePort() != 22 {
|
|
t.Errorf("default sftp port = %d, want 22", p.effectivePort())
|
|
}
|
|
p.protocol = protoFTP
|
|
if p.effectivePort() != 21 {
|
|
t.Errorf("default ftp port = %d, want 21", p.effectivePort())
|
|
}
|
|
}
|
|
|
|
func TestResolve(t *testing.T) {
|
|
p := &Plugin{basePath: "/uploads"}
|
|
cases := map[string]string{
|
|
"": "/uploads",
|
|
"a/b.txt": "/uploads/a/b.txt",
|
|
"/etc/abs": "/etc/abs",
|
|
}
|
|
for in, want := range cases {
|
|
if got := p.resolve(in); got != want {
|
|
t.Errorf("resolve(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestHealthCheckUnreachable confirms an unreachable host is classified as down
|
|
// (not a panic) — the graceful-failure path Init/HealthCheck must guarantee.
|
|
func TestHealthCheckUnreachable(t *testing.T) {
|
|
p := &Plugin{}
|
|
// Port 1 is reserved and refuses connections quickly.
|
|
if err := p.Init(context.Background(), map[string]string{
|
|
"protocol": protoSFTP, "host": "127.0.0.1", "port": "1",
|
|
"username": "u", "password": "pw",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h := p.HealthCheck(context.Background())
|
|
if h.Status != plugins.StatusDown {
|
|
t.Errorf("status = %q, want down (detail=%q)", h.Status, h.Detail)
|
|
}
|
|
}
|
|
|
|
func TestHostKeyFingerprintMismatch(t *testing.T) {
|
|
cb, err := hostKeyChecker("SHA256:doesnotmatch")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cb == nil {
|
|
t.Fatal("expected a callback")
|
|
}
|
|
}
|
|
|
|
// TestRegistered confirms the plugin registered itself with the shared registry
|
|
// via init(), so the manager will surface it.
|
|
func TestRegistered(t *testing.T) {
|
|
m := plugins.NewManager(t.TempDir() + "/plugins.json")
|
|
if _, ok := m.Get("filetransfer"); !ok {
|
|
t.Fatal("filetransfer not registered in the plugin manager")
|
|
}
|
|
}
|