package gonexapp_test import ( "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "strings" "testing" "gitea.neitzel.de/konrad/go-nc-exapp" ) func authUserFromRequest(r *http.Request) string { raw := r.Header.Get("AUTHORIZATION-APP-API") decoded, err := base64.StdEncoding.DecodeString(raw) if err != nil { return "" } parts := strings.SplitN(string(decoded), ":", 2) if len(parts) < 1 { return "" } return parts[0] } func TestAppAPINotificationsSendPostsForCredentialsUser(t *testing.T) { var ( gotMethod string gotPath string gotUser string gotBody []byte ) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotMethod = r.Method gotPath = r.URL.Path gotUser = authUserFromRequest(r) gotBody, _ = io.ReadAll(r.Body) _ = json.NewEncoder(w).Encode(map[string]any{"ocs": map[string]any{"data": map[string]any{}}}) })) t.Cleanup(srv.Close) cred := gonexapp.Credentials{ BaseURL: srv.URL, AppID: "app", AppVersion: "0.1.0", AAVersion: "1.0.0", AppSecret: "s", UserID: "alice", } api := gonexapp.NewAppAPINotifications(cred) api.Client = srv.Client() api.OCS.Client = srv.Client() err := api.Send(gonexapp.Notification{Subject: "Hello"}) if err != nil { t.Fatal(err) } if gotMethod != http.MethodPost { t.Fatalf("method=%q", gotMethod) } if !strings.Contains(gotPath, "apps/app_api/api/v1/notification") { t.Fatalf("path=%q", gotPath) } if gotUser != "alice" { t.Fatalf("auth user=%q", gotUser) } var payload struct { Params struct { Object string `json:"object"` ObjectID string `json:"object_id"` SubjectType string `json:"subject_type"` SubjectParams struct { RichSubject string `json:"rich_subject"` RichSubjectParams map[string]any `json:"rich_subject_params"` } `json:"subject_params"` } `json:"params"` } if err := json.Unmarshal(gotBody, &payload); err != nil { t.Fatalf("body: %v\n%s", err, gotBody) } if payload.Params.Object != "app_api" { t.Fatalf("object=%q", payload.Params.Object) } if payload.Params.ObjectID != "app_api_id" { t.Fatalf("object_id=%q", payload.Params.ObjectID) } if payload.Params.SubjectType != "app_api_ex_app" { t.Fatalf("subject_type=%q", payload.Params.SubjectType) } if payload.Params.SubjectParams.RichSubject != "Hello" { t.Fatalf("rich_subject=%q", payload.Params.SubjectParams.RichSubject) } if payload.Params.SubjectParams.RichSubjectParams == nil { t.Fatal("rich_subject_params is null") } if len(payload.Params.SubjectParams.RichSubjectParams) != 0 { t.Fatalf("rich_subject_params=%#v", payload.Params.SubjectParams.RichSubjectParams) } } func TestAppAPINotificationsSendToImpersonatesGivenUser(t *testing.T) { var gotUser string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotUser = authUserFromRequest(r) _ = json.NewEncoder(w).Encode(map[string]any{"ocs": map[string]any{"data": map[string]any{}}}) })) t.Cleanup(srv.Close) cred := gonexapp.Credentials{ BaseURL: srv.URL, AppID: "app", AppVersion: "0.1.0", AAVersion: "1.0.0", AppSecret: "s", UserID: "alice", } api := gonexapp.NewAppAPINotifications(cred) api.Client = srv.Client() api.OCS.Client = srv.Client() err := api.SendTo("bob", gonexapp.Notification{Subject: "Hello"}) if err != nil { t.Fatal(err) } if gotUser != "bob" { t.Fatalf("auth user=%q, want bob", gotUser) } } func TestAppAPINotificationsRejectsEmptySubject(t *testing.T) { called := false srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true _ = json.NewEncoder(w).Encode(map[string]any{"ocs": map[string]any{"data": map[string]any{}}}) })) t.Cleanup(srv.Close) cred := gonexapp.Credentials{ BaseURL: srv.URL, AppID: "app", AppVersion: "0.1.0", AAVersion: "1.0.0", AppSecret: "s", UserID: "alice", } api := gonexapp.NewAppAPINotifications(cred) api.Client = srv.Client() api.OCS.Client = srv.Client() if err := api.Send(gonexapp.Notification{}); err == nil { t.Fatal("Send: expected error for empty Subject") } if err := api.SendTo("bob", gonexapp.Notification{Message: "no subject"}); err == nil { t.Fatal("SendTo: expected error for empty Subject") } if called { t.Fatal("OCS was called") } } func TestAppAPINotificationsSendRejectsEmptyUserID(t *testing.T) { called := false srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true _ = json.NewEncoder(w).Encode(map[string]any{"ocs": map[string]any{"data": map[string]any{}}}) })) t.Cleanup(srv.Close) cred := gonexapp.Credentials{ BaseURL: srv.URL, AppID: "app", AppVersion: "0.1.0", AAVersion: "1.0.0", AppSecret: "s", } api := gonexapp.NewAppAPINotifications(cred) api.Client = srv.Client() api.OCS.Client = srv.Client() if err := api.Send(gonexapp.Notification{Subject: "Hello"}); err == nil { t.Fatal("expected error for empty UserID") } if called { t.Fatal("OCS was called") } } func TestAppAPINotificationsSendToRejectsEmptyUserID(t *testing.T) { called := false srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true _ = json.NewEncoder(w).Encode(map[string]any{"ocs": map[string]any{"data": map[string]any{}}}) })) t.Cleanup(srv.Close) cred := gonexapp.Credentials{ BaseURL: srv.URL, AppID: "app", AppVersion: "0.1.0", AAVersion: "1.0.0", AppSecret: "s", UserID: "alice", } api := gonexapp.NewAppAPINotifications(cred) api.Client = srv.Client() api.OCS.Client = srv.Client() if err := api.SendTo("", gonexapp.Notification{Subject: "Hello"}); err == nil { t.Fatal("expected error for empty userID") } if called { t.Fatal("OCS was called") } } func TestAppAPINotificationsSendIncludesOptionalMessageAndLink(t *testing.T) { var gotBody []byte srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotBody, _ = io.ReadAll(r.Body) _ = json.NewEncoder(w).Encode(map[string]any{"ocs": map[string]any{"data": map[string]any{}}}) })) t.Cleanup(srv.Close) cred := gonexapp.Credentials{ BaseURL: srv.URL, AppID: "app", AppVersion: "0.1.0", AAVersion: "1.0.0", AppSecret: "s", UserID: "alice", } api := gonexapp.NewAppAPINotifications(cred) api.Client = srv.Client() api.OCS.Client = srv.Client() err := api.Send(gonexapp.Notification{ Subject: "Hello", Message: "Details here", Link: "https://cloud.example/apps/checkdns", }) if err != nil { t.Fatal(err) } var payload struct { Params struct { SubjectParams struct { RichMessage string `json:"rich_message"` RichMessageParams map[string]any `json:"rich_message_params"` Link string `json:"link"` } `json:"subject_params"` } `json:"params"` } if err := json.Unmarshal(gotBody, &payload); err != nil { t.Fatalf("body: %v\n%s", err, gotBody) } if payload.Params.SubjectParams.RichMessage != "Details here" { t.Fatalf("rich_message=%q", payload.Params.SubjectParams.RichMessage) } if payload.Params.SubjectParams.RichMessageParams == nil { t.Fatal("rich_message_params is null") } if payload.Params.SubjectParams.Link != "https://cloud.example/apps/checkdns" { t.Fatalf("link=%q", payload.Params.SubjectParams.Link) } } func TestAppAPINotificationsSendSurfacesOCSStatus(t *testing.T) { for _, code := range []int{http.StatusBadRequest, http.StatusForbidden} { t.Run(http.StatusText(code), func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "nope", code) })) t.Cleanup(srv.Close) cred := gonexapp.Credentials{ BaseURL: srv.URL, AppID: "app", AppVersion: "0.1.0", AAVersion: "1.0.0", AppSecret: "s", UserID: "alice", } api := gonexapp.NewAppAPINotifications(cred) api.Client = srv.Client() api.OCS.Client = srv.Client() err := api.Send(gonexapp.Notification{Subject: "Hello"}) if err == nil { t.Fatal("expected error") } want := fmt.Sprintf("%d", code) if !strings.Contains(err.Error(), want) { t.Fatalf("error %q missing status %s", err, want) } }) } }