fix(uploads): Club Log is configured — it never needed an API key

The guard I added for services with no credentials demanded one, and
nobody has ever set it: OpsLog carries its own Club Log APPLICATION key
(clublogAppAPIKey), so the account is an email, a password and the
logbook callsign. An operator whose live upload had been working for
months was told the service was not configured the moment he sent QSOs by
hand after an import.

Written in app.go, the rules drifted from the uploaders on the first try.
They now live in internal/extsvc beside the Upload* functions that
enforce them, each case mirroring that function's own guard — which also
caught Cloudlog, where the station profile is required and the check did
not ask for it. The message names the fields actually missing rather than
listing everything the service takes.
This commit is contained in:
2026-09-06 18:06:06 +02:00
parent e8f9e68759
commit 2808bead97
4 changed files with 136 additions and 40 deletions
+44 -5
View File
@@ -25,11 +25,50 @@ func TestUploadRefusesAnUnconfiguredService(t *testing.T) {
t.Errorf("%s: %q does not say where to fix it", svc, err)
}
}
}
// Configured: nothing in the way.
cfg := extsvc.ExternalServices{}
cfg.Cloudlog.URL, cfg.Cloudlog.APIKey = "https://log.f4bpo.fr", "cl123"
if err := uploadConfigured(extsvc.ServiceCloudlog, cfg); err != nil {
t.Errorf("a configured Cloudlog was refused: %v", err)
// And a CONFIGURED service is not turned away. Club Log is the one that was:
// its API key is OpsLog's own application key, embedded and never entered, so
// demanding it refused every operator who had the service working.
func TestAConfiguredServiceIsAccepted(t *testing.T) {
var cfg extsvc.ExternalServices
cfg.Clublog.Email, cfg.Clublog.Password, cfg.Clublog.Callsign = "[email protected]", "secret", "F4BPO"
cfg.QRZ.APIKey = "1234-5678"
cfg.Cloudlog.URL, cfg.Cloudlog.APIKey, cfg.Cloudlog.StationID = "https://log.example.com", "cl-key", "3"
cfg.EQSL.Username, cfg.EQSL.Password = "F4BPO", "secret"
cfg.HamQTH.Username, cfg.HamQTH.Password = "f4bpo", "secret"
cfg.HRDLog.Callsign, cfg.HRDLog.Code = "F4BPO", "12345"
cfg.LoTW.TQSLPath, cfg.LoTW.StationLocation = `C:\Program Files (x86)\TrustedQSL\tqsl.exe`, "Home"
for _, svc := range []extsvc.Service{
extsvc.ServiceCloudlog, extsvc.ServiceQRZ, extsvc.ServiceClublog,
extsvc.ServiceHRDLog, extsvc.ServiceEQSL, extsvc.ServiceHamQTH, extsvc.ServiceLoTW,
} {
if err := uploadConfigured(svc, cfg); err != nil {
t.Errorf("%s: a configured service was refused: %v", svc, err)
}
}
}
// The message names what is actually missing, so the operator opens the right
// field rather than checking three that were already filled in.
func TestTheRefusalNamesTheMissingFields(t *testing.T) {
var cfg extsvc.ExternalServices
cfg.Clublog.Email = "[email protected]"
err := uploadConfigured(extsvc.ServiceClublog, cfg)
if err == nil {
t.Fatal("a half-configured Club Log was accepted")
}
msg := err.Error()
if strings.Contains(msg, "email") {
t.Errorf("%q asks for the one field that IS set", msg)
}
for _, want := range []string{"password", "logbook callsign"} {
if !strings.Contains(msg, want) {
t.Errorf("%q does not mention the missing %s", msg, want)
}
}
if strings.Contains(strings.ToLower(msg), "api key") {
t.Errorf("%q asks for the API key — it is OpsLog's own, embedded", msg)
}
}