feat: add tenant entry registration APIs
This commit is contained in:
@@ -8,8 +8,10 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -17,6 +19,7 @@ import (
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/tinode/chat/server/auth"
|
||||
"github.com/tinode/chat/server/auth/mock_auth"
|
||||
"github.com/tinode/chat/server/media"
|
||||
"github.com/tinode/chat/server/store"
|
||||
"github.com/tinode/chat/server/store/mock_store"
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
@@ -39,6 +42,65 @@ type testHTTPInfrastructureStore struct {
|
||||
events []types.BusinessEvent
|
||||
}
|
||||
|
||||
type testTenantEntryStore struct {
|
||||
upload *types.PublicUpload
|
||||
application *types.TenantApplication
|
||||
feedback *types.TenantEntryFeedback
|
||||
}
|
||||
|
||||
func (s *testTenantEntryStore) CreatePublicUpload(upload *types.PublicUpload) (*types.PublicUpload, error) {
|
||||
now := time.Now().UTC().Round(time.Millisecond)
|
||||
upload.CreatedAt = now
|
||||
upload.UploadedAt = now
|
||||
s.upload = upload
|
||||
return upload, nil
|
||||
}
|
||||
|
||||
func (s *testTenantEntryStore) CreateTenantApplication(application *types.TenantApplication) (*types.TenantApplication, error) {
|
||||
now := time.Now().UTC().Round(time.Millisecond)
|
||||
application.Status = types.TenantApplicationStatusReceived
|
||||
application.EstimatedReviewHour = 24
|
||||
application.SubmittedAt = now
|
||||
s.application = application
|
||||
return application, nil
|
||||
}
|
||||
|
||||
func (s *testTenantEntryStore) CreateTenantEntryFeedback(feedback *types.TenantEntryFeedback) (*types.TenantEntryFeedback, error) {
|
||||
now := time.Now().UTC().Round(time.Millisecond)
|
||||
feedback.Status = types.TenantEntryFeedbackReceived
|
||||
feedback.SubmittedAt = now
|
||||
s.feedback = feedback
|
||||
return feedback, nil
|
||||
}
|
||||
|
||||
type testPublicMediaHandler struct {
|
||||
def *media.PublicFileDef
|
||||
data string
|
||||
}
|
||||
|
||||
func (h *testPublicMediaHandler) Init(string) error { return nil }
|
||||
func (h *testPublicMediaHandler) Headers(types.TenantID, string, *url.URL, http.Header, bool) (http.Header, int, error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
func (h *testPublicMediaHandler) Upload(*types.FileDef, io.Reader) (string, int64, error) {
|
||||
return "", 0, types.ErrUnsupported
|
||||
}
|
||||
func (h *testPublicMediaHandler) Download(types.TenantID, string) (*types.FileDef, media.ReadSeekCloser, error) {
|
||||
return nil, nil, types.ErrUnsupported
|
||||
}
|
||||
func (h *testPublicMediaHandler) Delete([]string) error { return nil }
|
||||
func (h *testPublicMediaHandler) GetIdFromUrl(string) types.Uid {
|
||||
return types.ZeroUid
|
||||
}
|
||||
func (h *testPublicMediaHandler) UploadPublic(def *media.PublicFileDef, file io.Reader) (string, int64, error) {
|
||||
data, _ := io.ReadAll(file)
|
||||
h.def = def
|
||||
h.data = string(data)
|
||||
def.Location = "public/" + def.Purpose + "/" + def.ID
|
||||
def.ETag = "etag"
|
||||
return def.Location, int64(len(data)), nil
|
||||
}
|
||||
|
||||
func (s *testHTTPInfrastructureStore) recordKey(record *types.HTTPIdempotencyRecord) string {
|
||||
return record.UserID.String() + "|" + record.Method + "|" + record.Route + "|" + record.Key
|
||||
}
|
||||
@@ -1357,6 +1419,117 @@ func TestServeIMTenantResolveHidesMissingTenant(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMTenantApplications(t *testing.T) {
|
||||
oldTenantEntry := store.TenantEntry
|
||||
entry := &testTenantEntryStore{}
|
||||
store.TenantEntry = entry
|
||||
defer func() { store.TenantEntry = oldTenantEntry }()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenant-applications", strings.NewReader(`{
|
||||
"credit_code":"91310000ma1k000000",
|
||||
"enterprise_email":"admin@example.com",
|
||||
"license_file_id":"file_123",
|
||||
"tenant_code_hint":"acme",
|
||||
"client_context":{"platform":"android"}
|
||||
}`))
|
||||
req.Header.Set("Idempotency-Key", "tenant-app-1")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMTenantApplications(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d, body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if entry.application == nil || entry.application.CreditCode != "91310000MA1K000000" ||
|
||||
entry.application.EnterpriseEmail != "admin@example.com" || entry.application.LicenseFileID != "file_123" {
|
||||
t.Fatalf("unexpected application: %#v", entry.application)
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if !strings.HasPrefix(data["application_id"].(string), "entapp_") || data["status"] != types.TenantApplicationStatusReceived {
|
||||
t.Fatalf("unexpected application response: %#v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMTenantEntryFeedback(t *testing.T) {
|
||||
oldTenantEntry := store.TenantEntry
|
||||
entry := &testTenantEntryStore{}
|
||||
store.TenantEntry = entry
|
||||
defer func() { store.TenantEntry = oldTenantEntry }()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenant-entry-feedback", strings.NewReader(`{
|
||||
"description":"企业码 acme 提示不可用,无法进入企业。",
|
||||
"contact":"user@example.com",
|
||||
"tenant_code_hint":"acme",
|
||||
"screenshot_file_ids":["file_1","file_1","file_2"],
|
||||
"client_context":{"platform":"ios"}
|
||||
}`))
|
||||
req.Header.Set("Idempotency-Key", "entry-feedback-1")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMTenantEntryFeedback(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d, body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if entry.feedback == nil || entry.feedback.Description == "" || len(entry.feedback.ScreenshotFileIDs) != 2 {
|
||||
t.Fatalf("unexpected feedback: %#v", entry.feedback)
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if !strings.HasPrefix(data["feedback_id"].(string), "entryfb_") || data["status"] != types.TenantEntryFeedbackReceived {
|
||||
t.Fatalf("unexpected feedback response: %#v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMPublicUploads(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
oldStore := store.Store
|
||||
oldTenantEntry := store.TenantEntry
|
||||
entry := &testTenantEntryStore{}
|
||||
handler := &testPublicMediaHandler{}
|
||||
mockStore := mock_store.NewMockPersistentStorageInterface(ctrl)
|
||||
mockStore.EXPECT().GetMediaHandler().Return(handler).AnyTimes()
|
||||
store.Store = mockStore
|
||||
store.TenantEntry = entry
|
||||
defer func() {
|
||||
store.Store = oldStore
|
||||
store.TenantEntry = oldTenantEntry
|
||||
}()
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
if err := writer.WriteField("purpose", types.PublicUploadPurposeTenantFeedback); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
part, err := writer.CreateFormFile("file", "shot.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := part.Write([]byte("\x89PNG\r\n\x1a\n" + strings.Repeat("\x00", 64))); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/public/uploads", &body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMPublicUploads(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d, body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if entry.upload == nil || entry.upload.Purpose != types.PublicUploadPurposeTenantFeedback ||
|
||||
entry.upload.Location == "" || handler.def == nil {
|
||||
t.Fatalf("unexpected upload: %#v handler=%#v", entry.upload, handler.def)
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if !strings.HasPrefix(data["file_id"].(string), "file_") || data["purpose"] != types.PublicUploadPurposeTenantFeedback {
|
||||
t.Fatalf("unexpected upload response: %#v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMVerificationCodesCreatesChallenge(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
oldVerifications := store.Verifications
|
||||
|
||||
Reference in New Issue
Block a user