forked from baron/baron-sso
285 lines
11 KiB
Go
285 lines
11 KiB
Go
package handler
|
|
|
|
import (
|
|
"baron-sso-backend/internal/domain"
|
|
"baron-sso-backend/internal/service"
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log/slog"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestWorksmobileHandlerRejectsNonHanmacTenant(t *testing.T) {
|
|
h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
|
|
overview: service.WorksmobileTenantOverview{
|
|
Tenant: domain.Tenant{ID: "tenant-1", Slug: "other"},
|
|
},
|
|
})
|
|
app := fiber.New()
|
|
app.Get("/tenants/:tenantId/worksmobile", h.GetOverview)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("GET", "/tenants/tenant-1/worksmobile", nil))
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
|
}
|
|
|
|
func TestWorksmobileHandlerReturnsOverviewForHanmacTenant(t *testing.T) {
|
|
h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
|
|
overview: service.WorksmobileTenantOverview{
|
|
Tenant: domain.Tenant{ID: "hanmac-id", Slug: "hanmac-family"},
|
|
Config: service.WorksmobileConfigSummary{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
})
|
|
app := fiber.New()
|
|
app.Get("/tenants/:tenantId/worksmobile", h.GetOverview)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("GET", "/tenants/hanmac-id/worksmobile", nil))
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
|
}
|
|
|
|
func TestWorksmobileHandlerDownloadsInitialPasswordCSV(t *testing.T) {
|
|
h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
|
|
credentials: []service.WorksmobileInitialPasswordCredential{
|
|
{
|
|
Email: "user@hanmaceng.co.kr",
|
|
Name: "홍길동",
|
|
PrimaryLeafOrgName: "인재성장",
|
|
InitialPassword: "Aa1!Aa1!Aa1!Aa1!",
|
|
Status: "processed",
|
|
},
|
|
},
|
|
})
|
|
app := fiber.New()
|
|
app.Get("/tenants/:tenantId/worksmobile/initial-passwords.csv", h.DownloadInitialPasswordsCSV)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("GET", "/tenants/hanmac-id/worksmobile/initial-passwords.csv", nil))
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
|
require.Contains(t, resp.Header.Get("Content-Disposition"), "worksmobile_initial_passwords.csv")
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(body), "email,name,primaryLeafOrgName,initialPassword,status,lastError")
|
|
require.Contains(t, string(body), "user@hanmaceng.co.kr,홍길동,인재성장,Aa1!Aa1!Aa1!Aa1!,processed,")
|
|
}
|
|
|
|
func TestWorksmobileHandlerPassesInitialPasswordBatchID(t *testing.T) {
|
|
fakeService := &fakeWorksmobileAdminService{
|
|
credentials: []service.WorksmobileInitialPasswordCredential{
|
|
{Email: "batch-user@hanmaceng.co.kr", InitialPassword: "BatchPass1!", Status: "pending"},
|
|
},
|
|
}
|
|
h := NewWorksmobileHandler(fakeService)
|
|
app := fiber.New()
|
|
app.Get("/tenants/:tenantId/worksmobile/initial-passwords.csv", h.DownloadInitialPasswordsCSV)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("GET", "/tenants/hanmac-id/worksmobile/initial-passwords.csv?batchId=batch-1", nil))
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
|
require.Equal(t, "batch-1", fakeService.downloadCredentialBatchID)
|
|
}
|
|
|
|
func TestWorksmobileHandlerPassesSyncUserCredentialBatchID(t *testing.T) {
|
|
fakeService := &fakeWorksmobileAdminService{}
|
|
h := NewWorksmobileHandler(fakeService)
|
|
app := fiber.New()
|
|
app.Post("/tenants/:tenantId/worksmobile/users/:userId/sync", h.SyncUser)
|
|
|
|
req := httptest.NewRequest("POST", "/tenants/hanmac-id/worksmobile/users/user-1/sync", strings.NewReader(`{"credentialBatchId":"batch-1","initialPassword":"InputPass1!"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := app.Test(req)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusAccepted, resp.StatusCode)
|
|
require.Equal(t, "batch-1", fakeService.syncUserCredentialBatchID)
|
|
require.Equal(t, "InputPass1!", fakeService.syncUserInitialPassword)
|
|
}
|
|
|
|
func TestWorksmobileHandlerPassesPasswordResetCredentialBatchID(t *testing.T) {
|
|
fakeService := &fakeWorksmobileAdminService{}
|
|
h := NewWorksmobileHandler(fakeService)
|
|
app := fiber.New()
|
|
app.Post("/tenants/:tenantId/worksmobile/users/:userId/password/reset", h.ResetUserPassword)
|
|
|
|
req := httptest.NewRequest("POST", "/tenants/hanmac-id/worksmobile/users/user-1/password/reset", strings.NewReader(`{"credentialBatchId":"batch-1"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := app.Test(req)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusAccepted, resp.StatusCode)
|
|
require.Equal(t, "batch-1", fakeService.resetPasswordCredentialBatchID)
|
|
}
|
|
|
|
func TestWorksmobileHandlerReturnsCredentialBatchHistory(t *testing.T) {
|
|
h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
|
|
credentialBatches: []service.WorksmobileCredentialBatch{
|
|
{BatchID: "batch-1", UserCount: 2, HasPasswords: true},
|
|
},
|
|
})
|
|
app := fiber.New()
|
|
app.Get("/tenants/:tenantId/worksmobile/credential-batches", h.ListCredentialBatches)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("GET", "/tenants/hanmac-id/worksmobile/credential-batches", nil))
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(body), `"batchId":"batch-1"`)
|
|
require.Contains(t, string(body), `"userCount":2`)
|
|
}
|
|
|
|
func TestWorksmobileHandlerDeletesCredentialBatchPasswords(t *testing.T) {
|
|
fakeService := &fakeWorksmobileAdminService{}
|
|
h := NewWorksmobileHandler(fakeService)
|
|
app := fiber.New()
|
|
app.Delete("/tenants/:tenantId/worksmobile/credential-batches/:batchId/passwords", h.DeleteCredentialBatchPasswords)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("DELETE", "/tenants/hanmac-id/worksmobile/credential-batches/batch-1/passwords", nil))
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
|
require.Equal(t, "batch-1", fakeService.deletedCredentialBatchID)
|
|
}
|
|
|
|
func TestWorksmobileHandlerDeletesPendingJobs(t *testing.T) {
|
|
fakeService := &fakeWorksmobileAdminService{
|
|
pendingJobsDeleteResult: service.WorksmobilePendingJobDeleteResult{DeletedCount: 3},
|
|
}
|
|
h := NewWorksmobileHandler(fakeService)
|
|
app := fiber.New()
|
|
app.Delete("/tenants/:tenantId/worksmobile/jobs/pending", h.DeletePendingJobs)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("DELETE", "/tenants/hanmac-id/worksmobile/jobs/pending", nil))
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
|
require.Equal(t, "hanmac-id", fakeService.deletedPendingJobsTenantID)
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(body), `"deletedCount":3`)
|
|
}
|
|
|
|
func TestWorksmobileHandlerLogsActionFailures(t *testing.T) {
|
|
var logs bytes.Buffer
|
|
previous := slog.Default()
|
|
slog.SetDefault(slog.New(slog.NewJSONHandler(&logs, nil)))
|
|
t.Cleanup(func() {
|
|
slog.SetDefault(previous)
|
|
})
|
|
|
|
h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
|
|
syncUserErr: errors.New("works user sync failed"),
|
|
})
|
|
app := fiber.New()
|
|
app.Post("/tenants/:tenantId/worksmobile/users/:userId/sync", h.SyncUser)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("POST", "/tenants/hanmac-id/worksmobile/users/user-1/sync", nil))
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
|
|
require.Contains(t, logs.String(), "worksmobile admin operation failed")
|
|
require.Contains(t, logs.String(), "sync_user")
|
|
require.Contains(t, logs.String(), "works user sync failed")
|
|
}
|
|
|
|
func TestWorksmobileHandlerReturnsBadRequestForOutOfScopeUserSync(t *testing.T) {
|
|
h := NewWorksmobileHandler(&fakeWorksmobileAdminService{
|
|
syncUserErr: errors.New("target user tenant is excluded from Worksmobile sync"),
|
|
})
|
|
app := fiber.New()
|
|
app.Post("/tenants/:tenantId/worksmobile/users/:userId/sync", h.SyncUser)
|
|
|
|
resp, err := app.Test(httptest.NewRequest("POST", "/tenants/hanmac-id/worksmobile/users/user-1/sync", nil))
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, fiber.StatusBadRequest, resp.StatusCode)
|
|
}
|
|
|
|
type fakeWorksmobileAdminService struct {
|
|
overview service.WorksmobileTenantOverview
|
|
credentials []service.WorksmobileInitialPasswordCredential
|
|
syncUserErr error
|
|
syncUserCredentialBatchID string
|
|
syncUserInitialPassword string
|
|
resetPasswordCredentialBatchID string
|
|
downloadCredentialBatchID string
|
|
deletedCredentialBatchID string
|
|
deletedPendingJobsTenantID string
|
|
pendingJobsDeleteResult service.WorksmobilePendingJobDeleteResult
|
|
credentialBatches []service.WorksmobileCredentialBatch
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) GetTenantOverview(ctx context.Context, tenantID string) (service.WorksmobileTenantOverview, error) {
|
|
return f.overview, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) GetComparison(ctx context.Context, tenantID string, includeMatched bool) (service.WorksmobileComparison, error) {
|
|
return service.WorksmobileComparison{}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) ImportUsersFromWorks(ctx context.Context, tenantID string, worksmobileUserIDs []string) (service.WorksmobileImportUsersResult, error) {
|
|
return service.WorksmobileImportUsersResult{UpdatedCount: len(worksmobileUserIDs)}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) EnqueueBackfillDryRun(ctx context.Context, tenantID string) (service.WorksmobileBackfillDryRun, error) {
|
|
return service.WorksmobileBackfillDryRun{}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) EnqueueOrgUnitSync(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error) {
|
|
return &domain.WorksmobileOutbox{ID: "job-orgunit", ResourceID: orgUnitID}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) EnqueueOrgUnitDelete(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error) {
|
|
return &domain.WorksmobileOutbox{ID: "job-orgunit-delete", ResourceID: orgUnitID, Action: domain.WorksmobileActionDelete}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) EnqueueUserSync(ctx context.Context, tenantID, userID, credentialBatchID, initialPassword string) (*domain.WorksmobileOutbox, error) {
|
|
f.syncUserCredentialBatchID = credentialBatchID
|
|
f.syncUserInitialPassword = initialPassword
|
|
if f.syncUserErr != nil {
|
|
return nil, f.syncUserErr
|
|
}
|
|
return &domain.WorksmobileOutbox{ID: "job-user", ResourceID: userID}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) EnqueueUserPasswordReset(ctx context.Context, tenantID, userID, credentialBatchID string) (*domain.WorksmobileOutbox, error) {
|
|
f.resetPasswordCredentialBatchID = credentialBatchID
|
|
return &domain.WorksmobileOutbox{ID: "job-user-password-reset", ResourceID: userID}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) RetryJob(ctx context.Context, tenantID, jobID string) (*domain.WorksmobileOutbox, error) {
|
|
return &domain.WorksmobileOutbox{ID: jobID}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) ListInitialPasswordCredentials(ctx context.Context, tenantID, credentialBatchID string) ([]service.WorksmobileInitialPasswordCredential, error) {
|
|
f.downloadCredentialBatchID = credentialBatchID
|
|
return f.credentials, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) ListCredentialBatches(ctx context.Context, tenantID string) ([]service.WorksmobileCredentialBatch, error) {
|
|
return f.credentialBatches, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) DeleteCredentialBatchPasswords(ctx context.Context, tenantID, credentialBatchID string) (service.WorksmobileCredentialBatch, error) {
|
|
f.deletedCredentialBatchID = credentialBatchID
|
|
return service.WorksmobileCredentialBatch{BatchID: credentialBatchID}, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileAdminService) DeletePendingJobs(ctx context.Context, tenantID string) (service.WorksmobilePendingJobDeleteResult, error) {
|
|
f.deletedPendingJobsTenantID = tenantID
|
|
return f.pendingJobsDeleteResult, nil
|
|
}
|