feat: expose auth identity migration reports
This commit is contained in:
@@ -22,6 +22,8 @@ func setupAdminRouter() (*gin.Engine, *stubAdminService) {
|
|||||||
redeemHandler := NewRedeemHandler(adminSvc, nil)
|
redeemHandler := NewRedeemHandler(adminSvc, nil)
|
||||||
|
|
||||||
router.GET("/api/v1/admin/users", userHandler.List)
|
router.GET("/api/v1/admin/users", userHandler.List)
|
||||||
|
router.GET("/api/v1/admin/users/auth-identity-migration-reports/summary", userHandler.GetAuthIdentityMigrationReportSummary)
|
||||||
|
router.GET("/api/v1/admin/users/auth-identity-migration-reports", userHandler.ListAuthIdentityMigrationReports)
|
||||||
router.GET("/api/v1/admin/users/:id", userHandler.GetByID)
|
router.GET("/api/v1/admin/users/:id", userHandler.GetByID)
|
||||||
router.POST("/api/v1/admin/users", userHandler.Create)
|
router.POST("/api/v1/admin/users", userHandler.Create)
|
||||||
router.PUT("/api/v1/admin/users/:id", userHandler.Update)
|
router.PUT("/api/v1/admin/users/:id", userHandler.Update)
|
||||||
@@ -70,6 +72,16 @@ func TestUserHandlerEndpoints(t *testing.T) {
|
|||||||
router.ServeHTTP(rec, req)
|
router.ServeHTTP(rec, req)
|
||||||
require.Equal(t, http.StatusOK, rec.Code)
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/users/auth-identity-migration-reports/summary", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/users/auth-identity-migration-reports?report_type=oidc_synthetic_email_requires_manual_recovery", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
rec = httptest.NewRecorder()
|
rec = httptest.NewRecorder()
|
||||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/users/1", nil)
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/users/1", nil)
|
||||||
router.ServeHTTP(rec, req)
|
router.ServeHTTP(rec, req)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type stubAdminService struct {
|
|||||||
proxies []service.Proxy
|
proxies []service.Proxy
|
||||||
proxyCounts []service.ProxyWithAccountCount
|
proxyCounts []service.ProxyWithAccountCount
|
||||||
redeems []service.RedeemCode
|
redeems []service.RedeemCode
|
||||||
|
migrationReports []service.AuthIdentityMigrationReport
|
||||||
createdAccounts []*service.CreateAccountInput
|
createdAccounts []*service.CreateAccountInput
|
||||||
createdProxies []*service.CreateProxyInput
|
createdProxies []*service.CreateProxyInput
|
||||||
updatedProxyIDs []int64
|
updatedProxyIDs []int64
|
||||||
@@ -123,6 +124,15 @@ func newStubAdminService() *stubAdminService {
|
|||||||
proxies: []service.Proxy{proxy},
|
proxies: []service.Proxy{proxy},
|
||||||
proxyCounts: []service.ProxyWithAccountCount{{Proxy: proxy, AccountCount: 1}},
|
proxyCounts: []service.ProxyWithAccountCount{{Proxy: proxy, AccountCount: 1}},
|
||||||
redeems: []service.RedeemCode{redeem},
|
redeems: []service.RedeemCode{redeem},
|
||||||
|
migrationReports: []service.AuthIdentityMigrationReport{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
ReportType: "oidc_synthetic_email_requires_manual_recovery",
|
||||||
|
ReportKey: "u-1",
|
||||||
|
Details: map[string]any{"user_id": 1},
|
||||||
|
CreatedAt: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +177,30 @@ func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64,
|
|||||||
return map[string]any{"user_id": userID}, nil
|
return map[string]any{"user_id": userID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) ListAuthIdentityMigrationReports(ctx context.Context, reportType string, page, pageSize int) ([]service.AuthIdentityMigrationReport, int64, error) {
|
||||||
|
if reportType == "" {
|
||||||
|
return s.migrationReports, int64(len(s.migrationReports)), nil
|
||||||
|
}
|
||||||
|
filtered := make([]service.AuthIdentityMigrationReport, 0, len(s.migrationReports))
|
||||||
|
for _, report := range s.migrationReports {
|
||||||
|
if strings.EqualFold(report.ReportType, reportType) {
|
||||||
|
filtered = append(filtered, report)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered, int64(len(filtered)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubAdminService) GetAuthIdentityMigrationReportSummary(ctx context.Context) (*service.AuthIdentityMigrationReportSummary, error) {
|
||||||
|
summary := &service.AuthIdentityMigrationReportSummary{
|
||||||
|
ByType: map[string]int64{},
|
||||||
|
}
|
||||||
|
for _, report := range s.migrationReports {
|
||||||
|
summary.Total++
|
||||||
|
summary.ByType[report.ReportType]++
|
||||||
|
}
|
||||||
|
return summary, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]service.Group, int64, error) {
|
func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]service.Group, int64, error) {
|
||||||
return s.groups, int64(len(s.groups)), nil
|
return s.groups, int64(len(s.groups)), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,6 +172,31 @@ func (h *UserHandler) GetByID(c *gin.Context) {
|
|||||||
response.Success(c, dto.UserFromServiceAdmin(user))
|
response.Success(c, dto.UserFromServiceAdmin(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAuthIdentityMigrationReportSummary returns aggregate migration report counts.
|
||||||
|
// GET /api/v1/admin/users/auth-identity-migration-reports/summary
|
||||||
|
func (h *UserHandler) GetAuthIdentityMigrationReportSummary(c *gin.Context) {
|
||||||
|
summary, err := h.adminService.GetAuthIdentityMigrationReportSummary(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAuthIdentityMigrationReports returns paginated auth identity migration reports.
|
||||||
|
// GET /api/v1/admin/users/auth-identity-migration-reports
|
||||||
|
func (h *UserHandler) ListAuthIdentityMigrationReports(c *gin.Context) {
|
||||||
|
page, pageSize := response.ParsePagination(c)
|
||||||
|
reportType := strings.TrimSpace(c.Query("report_type"))
|
||||||
|
|
||||||
|
reports, total, err := h.adminService.ListAuthIdentityMigrationReports(c.Request.Context(), reportType, page, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
response.ErrorFrom(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Paginated(c, reports, total, page, pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
// Create handles creating a new user
|
// Create handles creating a new user
|
||||||
// POST /api/v1/admin/users
|
// POST /api/v1/admin/users
|
||||||
func (h *UserHandler) Create(c *gin.Context) {
|
func (h *UserHandler) Create(c *gin.Context) {
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ func registerDashboardRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
|
|||||||
func registerUserManagementRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
|
func registerUserManagementRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
|
||||||
users := admin.Group("/users")
|
users := admin.Group("/users")
|
||||||
{
|
{
|
||||||
|
users.GET("/auth-identity-migration-reports/summary", h.Admin.User.GetAuthIdentityMigrationReportSummary)
|
||||||
|
users.GET("/auth-identity-migration-reports", h.Admin.User.ListAuthIdentityMigrationReports)
|
||||||
users.GET("", h.Admin.User.List)
|
users.GET("", h.Admin.User.List)
|
||||||
users.GET("/:id", h.Admin.User.GetByID)
|
users.GET("/:id", h.Admin.User.GetByID)
|
||||||
users.POST("", h.Admin.User.Create)
|
users.POST("", h.Admin.User.Create)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -16,6 +18,8 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/util/httputil"
|
"github.com/Wei-Shaw/sub2api/internal/util/httputil"
|
||||||
|
|
||||||
|
entsql "entgo.io/ent/dialect/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AdminService interface defines admin management operations
|
// AdminService interface defines admin management operations
|
||||||
@@ -33,6 +37,8 @@ type AdminService interface {
|
|||||||
// codeType is optional - pass empty string to return all types.
|
// codeType is optional - pass empty string to return all types.
|
||||||
// Also returns totalRecharged (sum of all positive balance top-ups).
|
// Also returns totalRecharged (sum of all positive balance top-ups).
|
||||||
GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]RedeemCode, int64, float64, error)
|
GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]RedeemCode, int64, float64, error)
|
||||||
|
ListAuthIdentityMigrationReports(ctx context.Context, reportType string, page, pageSize int) ([]AuthIdentityMigrationReport, int64, error)
|
||||||
|
GetAuthIdentityMigrationReportSummary(ctx context.Context) (*AuthIdentityMigrationReportSummary, error)
|
||||||
|
|
||||||
// Group management
|
// Group management
|
||||||
ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error)
|
ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error)
|
||||||
@@ -127,6 +133,19 @@ type UpdateUserInput struct {
|
|||||||
GroupRates map[int64]*float64
|
GroupRates map[int64]*float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AuthIdentityMigrationReport struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ReportType string `json:"report_type"`
|
||||||
|
ReportKey string `json:"report_key"`
|
||||||
|
Details map[string]any `json:"details"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthIdentityMigrationReportSummary struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
ByType map[string]int64 `json:"by_type"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateGroupInput struct {
|
type CreateGroupInput struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
@@ -788,6 +807,122 @@ func (s *adminServiceImpl) GetUserBalanceHistory(ctx context.Context, userID int
|
|||||||
return codes, result.Total, totalRecharged, nil
|
return codes, result.Total, totalRecharged, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *adminServiceImpl) ListAuthIdentityMigrationReports(ctx context.Context, reportType string, page, pageSize int) ([]AuthIdentityMigrationReport, int64, error) {
|
||||||
|
db, err := s.adminSQLDB()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reportType = strings.TrimSpace(reportType)
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
if err := db.QueryRowContext(ctx, `
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM auth_identity_migration_reports
|
||||||
|
WHERE ($1 = '' OR report_type = $1)`,
|
||||||
|
reportType,
|
||||||
|
).Scan(&total); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.QueryContext(ctx, `
|
||||||
|
SELECT id, report_type, report_key, details, created_at
|
||||||
|
FROM auth_identity_migration_reports
|
||||||
|
WHERE ($1 = '' OR report_type = $1)
|
||||||
|
ORDER BY created_at DESC, id DESC
|
||||||
|
LIMIT $2 OFFSET $3`,
|
||||||
|
reportType,
|
||||||
|
pageSize,
|
||||||
|
offset,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
defer func() { _ = rows.Close() }()
|
||||||
|
|
||||||
|
reports := make([]AuthIdentityMigrationReport, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
report, scanErr := scanAuthIdentityMigrationReport(rows)
|
||||||
|
if scanErr != nil {
|
||||||
|
return nil, 0, scanErr
|
||||||
|
}
|
||||||
|
reports = append(reports, report)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return reports, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *adminServiceImpl) GetAuthIdentityMigrationReportSummary(ctx context.Context) (*AuthIdentityMigrationReportSummary, error) {
|
||||||
|
db, err := s.adminSQLDB()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.QueryContext(ctx, `
|
||||||
|
SELECT report_type, COUNT(*)
|
||||||
|
FROM auth_identity_migration_reports
|
||||||
|
GROUP BY report_type
|
||||||
|
ORDER BY report_type ASC`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() { _ = rows.Close() }()
|
||||||
|
|
||||||
|
summary := &AuthIdentityMigrationReportSummary{
|
||||||
|
ByType: make(map[string]int64),
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var reportType string
|
||||||
|
var count int64
|
||||||
|
if err := rows.Scan(&reportType, &count); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
summary.ByType[reportType] = count
|
||||||
|
summary.Total += count
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return summary, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *adminServiceImpl) adminSQLDB() (*sql.DB, error) {
|
||||||
|
if s == nil || s.entClient == nil {
|
||||||
|
return nil, infraerrors.ServiceUnavailable("ADMIN_SQL_NOT_READY", "admin sql access is not ready")
|
||||||
|
}
|
||||||
|
driver, ok := s.entClient.Driver().(*entsql.Driver)
|
||||||
|
if !ok || driver.DB() == nil {
|
||||||
|
return nil, infraerrors.ServiceUnavailable("ADMIN_SQL_NOT_READY", "admin sql access is not ready")
|
||||||
|
}
|
||||||
|
return driver.DB(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanAuthIdentityMigrationReport(scanner interface{ Scan(dest ...any) error }) (AuthIdentityMigrationReport, error) {
|
||||||
|
var (
|
||||||
|
report AuthIdentityMigrationReport
|
||||||
|
details []byte
|
||||||
|
)
|
||||||
|
if err := scanner.Scan(&report.ID, &report.ReportType, &report.ReportKey, &details, &report.CreatedAt); err != nil {
|
||||||
|
return AuthIdentityMigrationReport{}, err
|
||||||
|
}
|
||||||
|
report.Details = map[string]any{}
|
||||||
|
if len(details) > 0 {
|
||||||
|
if err := json.Unmarshal(details, &report.Details); err != nil {
|
||||||
|
return AuthIdentityMigrationReport{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Group management implementations
|
// Group management implementations
|
||||||
func (s *adminServiceImpl) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error) {
|
func (s *adminServiceImpl) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error) {
|
||||||
params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
|
params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/enttest"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"entgo.io/ent/dialect"
|
||||||
|
entsql "entgo.io/ent/dialect/sql"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newAdminServiceMigrationReportTestClient(t *testing.T) *dbent.Client {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", "file:admin_service_migration_reports?mode=memory&cache=shared&_fk=1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = db.Close() })
|
||||||
|
|
||||||
|
_, err = db.Exec("PRAGMA foreign_keys = ON")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = db.Exec(`CREATE TABLE auth_identity_migration_reports (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
report_type TEXT NOT NULL,
|
||||||
|
report_key TEXT NOT NULL,
|
||||||
|
details TEXT NOT NULL DEFAULT '{}',
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
)`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
drv := entsql.OpenDB(dialect.SQLite, db)
|
||||||
|
client := enttest.NewClient(t, enttest.WithOptions(dbent.Driver(drv)))
|
||||||
|
t.Cleanup(func() { _ = client.Close() })
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminServiceListAuthIdentityMigrationReports(t *testing.T) {
|
||||||
|
client := newAdminServiceMigrationReportTestClient(t)
|
||||||
|
driver, ok := client.Driver().(*entsql.Driver)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
_, err := driver.DB().ExecContext(context.Background(), `
|
||||||
|
INSERT INTO auth_identity_migration_reports (report_type, report_key, details, created_at)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4),
|
||||||
|
($5, $6, $7, $8)`,
|
||||||
|
"oidc_synthetic_email_requires_manual_recovery", "u-1", `{"user_id":1}`, now,
|
||||||
|
"wechat_provider_key_conflict", "u-2", `{"user_id":2}`, now.Add(-time.Minute),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
svc := &adminServiceImpl{entClient: client}
|
||||||
|
reports, total, err := svc.ListAuthIdentityMigrationReports(context.Background(), "oidc_synthetic_email_requires_manual_recovery", 1, 20)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(1), total)
|
||||||
|
require.Len(t, reports, 1)
|
||||||
|
require.Equal(t, "oidc_synthetic_email_requires_manual_recovery", reports[0].ReportType)
|
||||||
|
require.Equal(t, float64(1), reports[0].Details["user_id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminServiceGetAuthIdentityMigrationReportSummary(t *testing.T) {
|
||||||
|
client := newAdminServiceMigrationReportTestClient(t)
|
||||||
|
driver, ok := client.Driver().(*entsql.Driver)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
_, err := driver.DB().ExecContext(context.Background(), `
|
||||||
|
INSERT INTO auth_identity_migration_reports (report_type, report_key, details, created_at)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4),
|
||||||
|
($5, $6, $7, $8),
|
||||||
|
($9, $10, $11, $12)`,
|
||||||
|
"oidc_synthetic_email_requires_manual_recovery", "u-1", `{"user_id":1}`, now,
|
||||||
|
"wechat_provider_key_conflict", "u-2", `{"user_id":2}`, now.Add(-time.Minute),
|
||||||
|
"wechat_provider_key_conflict", "u-3", `{"user_id":3}`, now.Add(-2*time.Minute),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
svc := &adminServiceImpl{entClient: client}
|
||||||
|
summary, err := svc.GetAuthIdentityMigrationReportSummary(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(3), summary.Total)
|
||||||
|
require.Equal(t, int64(1), summary.ByType["oidc_synthetic_email_requires_manual_recovery"])
|
||||||
|
require.Equal(t, int64(2), summary.ByType["wechat_provider_key_conflict"])
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user