Skip to content

Commit 3244dc1

Browse files
gqcnclaude
andcommitted
fix(tools/linactl): fix plugin-full CI aggregate module and improve plugin CLI output
Generate an aggregate `lina-plugins` module that blank-imports official source-plugin backends so the host `official_plugins` build tag resolves in plugin-full CI. Skip the generated module in `goWorkspaceModules` to avoid running its empty test suite. Improve `plugins.install` and `plugins.status` with progress messages and aligned ASCII table output. Inject host services into plugin test fixtures for source-plugin cron collection and auth runtime-parameter tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a6d4877 commit 3244dc1

12 files changed

Lines changed: 611 additions & 22 deletions

File tree

apps/lina-core/internal/service/auth/auth_runtime_params_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import (
2323
"lina-core/internal/service/orgcap"
2424
pluginsvc "lina-core/internal/service/plugin"
2525
"lina-core/internal/service/session"
26+
"lina-core/pkg/pluginhost"
27+
pluginservicebizctx "lina-core/pkg/pluginservice/bizctx"
28+
pluginserviceconfig "lina-core/pkg/pluginservice/config"
29+
plugincontract "lina-core/pkg/pluginservice/contract"
30+
pluginservicetenantfilter "lina-core/pkg/pluginservice/tenantfilter"
2631
)
2732

2833
// TestLoginRejectsBlacklistedIP verifies managed login IP blacklist settings
@@ -54,10 +59,107 @@ func newRuntimeParamAuthTestService() Service {
5459
i18nSvc := i18nsvc.New(bizCtxSvc, configSvc, cacheCoordSvc)
5560
sessionStore := session.NewDBStore()
5661
pluginSvc := pluginsvc.New(nil, configSvc, bizCtxSvc, cacheCoordSvc, i18nSvc, sessionStore)
62+
pluginSvc.SetHostServices(newRuntimeParamAuthTestHostServices(i18nSvc))
5763
cacheSvc := kvcache.New()
5864
return New(configSvc, pluginSvc, orgcap.New(pluginSvc), roleTestService{}, disabledTenantAuthTestService{}, sessionStore, cacheSvc)
5965
}
6066

67+
// runtimeParamAuthTestHostServices publishes the host services required by
68+
// official source-plugin auth hooks during plugin-full tests.
69+
type runtimeParamAuthTestHostServices struct {
70+
config plugincontract.ConfigService
71+
i18n plugincontract.I18nService
72+
tenantFilter plugincontract.TenantFilterService
73+
}
74+
75+
// Ensure runtimeParamAuthTestHostServices satisfies the source-plugin directory.
76+
var _ pluginhost.HostServices = (*runtimeParamAuthTestHostServices)(nil)
77+
78+
// newRuntimeParamAuthTestHostServices creates the minimal source-plugin host
79+
// service directory needed by auth runtime-parameter tests.
80+
func newRuntimeParamAuthTestHostServices(i18nSvc i18nsvc.Service) pluginhost.HostServices {
81+
bizCtxSvc := pluginservicebizctx.New(nil)
82+
return &runtimeParamAuthTestHostServices{
83+
config: pluginserviceconfig.New(),
84+
i18n: runtimeParamAuthTestI18n{service: i18nSvc},
85+
tenantFilter: pluginservicetenantfilter.New(bizCtxSvc, nil),
86+
}
87+
}
88+
89+
// APIDoc returns no apidoc service for auth runtime-parameter tests.
90+
func (s *runtimeParamAuthTestHostServices) APIDoc() plugincontract.APIDocService { return nil }
91+
92+
// Auth returns no auth service for auth runtime-parameter tests.
93+
func (s *runtimeParamAuthTestHostServices) Auth() plugincontract.AuthService { return nil }
94+
95+
// BizCtx returns no bizctx service for auth runtime-parameter tests.
96+
func (s *runtimeParamAuthTestHostServices) BizCtx() plugincontract.BizCtxService { return nil }
97+
98+
// Config returns the test host configuration service.
99+
func (s *runtimeParamAuthTestHostServices) Config() plugincontract.ConfigService {
100+
if s == nil {
101+
return nil
102+
}
103+
return s.config
104+
}
105+
106+
// I18n returns the runtime translation adapter used by auth hooks.
107+
func (s *runtimeParamAuthTestHostServices) I18n() plugincontract.I18nService {
108+
if s == nil {
109+
return nil
110+
}
111+
return s.i18n
112+
}
113+
114+
// Notify returns no notification service for auth runtime-parameter tests.
115+
func (s *runtimeParamAuthTestHostServices) Notify() plugincontract.NotifyService { return nil }
116+
117+
// PluginState returns no plugin-state service for auth runtime-parameter tests.
118+
func (s *runtimeParamAuthTestHostServices) PluginState() plugincontract.PluginStateService {
119+
return nil
120+
}
121+
122+
// Route returns no route service for auth runtime-parameter tests.
123+
func (s *runtimeParamAuthTestHostServices) Route() plugincontract.RouteService { return nil }
124+
125+
// Session returns no session service for auth runtime-parameter tests.
126+
func (s *runtimeParamAuthTestHostServices) Session() plugincontract.SessionService { return nil }
127+
128+
// TenantFilter returns the tenant-filter service used by auth hooks.
129+
func (s *runtimeParamAuthTestHostServices) TenantFilter() plugincontract.TenantFilterService {
130+
if s == nil {
131+
return nil
132+
}
133+
return s.tenantFilter
134+
}
135+
136+
// runtimeParamAuthTestI18n adapts internal i18n to the source-plugin contract
137+
// for auth runtime-parameter tests.
138+
type runtimeParamAuthTestI18n struct {
139+
service i18nsvc.Service
140+
}
141+
142+
// GetLocale returns the effective request locale.
143+
func (s runtimeParamAuthTestI18n) GetLocale(ctx context.Context) string {
144+
if s.service == nil {
145+
return i18nsvc.DefaultLocale
146+
}
147+
return s.service.GetLocale(ctx)
148+
}
149+
150+
// Translate resolves one runtime message.
151+
func (s runtimeParamAuthTestI18n) Translate(ctx context.Context, key string, fallback string) string {
152+
if s.service == nil {
153+
return fallback
154+
}
155+
return s.service.Translate(ctx, key, fallback)
156+
}
157+
158+
// FindMessageKeys is unused by auth hooks and returns no matches.
159+
func (runtimeParamAuthTestI18n) FindMessageKeys(context.Context, string, string) []string {
160+
return []string{}
161+
}
162+
61163
// newRequestContext builds one request-backed context carrying the supplied
62164
// remote address for auth service tests.
63165
func newRequestContext(t *testing.T, remoteAddr string) context.Context {

apps/lina-core/internal/service/plugin/internal/integration/extensions_cron_test.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"lina-core/internal/service/plugin/internal/catalog"
11+
"lina-core/internal/service/plugin/internal/integration"
1112
"lina-core/internal/service/plugin/internal/testutil"
1213
"lina-core/pkg/pluginbridge"
1314
)
@@ -67,13 +68,28 @@ func TestListManagedCronJobsSkipsDynamicDiscoveryForSourcePlugins(t *testing.T)
6768
t.Fatal("expected at least one source plugin manifest in test repository")
6869
}
6970

70-
if _, err = services.Integration.ListManagedCronJobs(context.Background()); err != nil {
71+
items, err := services.Integration.ListManagedCronJobs(context.Background())
72+
if err != nil {
7173
t.Fatalf("expected managed cron listing to succeed, got error: %v", err)
7274
}
75+
if !managedCronListContainsPlugin(items, pluginID) {
76+
t.Fatalf("expected managed cron list to include source plugin %s", pluginID)
77+
}
7378

7479
for _, pluginID := range executor.discoverPluginIDs {
7580
if _, exists := sourcePluginIDs[pluginID]; exists {
7681
t.Fatalf("expected source plugin %s to skip dynamic cron discovery", pluginID)
7782
}
7883
}
7984
}
85+
86+
// managedCronListContainsPlugin reports whether a managed cron list includes
87+
// at least one definition owned by pluginID.
88+
func managedCronListContainsPlugin(items []integration.ManagedCronJob, pluginID string) bool {
89+
for _, item := range items {
90+
if item.PluginID == pluginID {
91+
return true
92+
}
93+
}
94+
return false
95+
}

apps/lina-core/internal/service/plugin/internal/testutil/testutil_fixture.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
package testutil
44

55
import (
6+
"context"
67
"os"
78
"path/filepath"
89
"strings"
910
"testing"
1011

12+
"github.com/gogf/gf/v2/errors/gerror"
13+
1114
"lina-core/internal/service/plugin/internal/catalog"
1215
"lina-core/internal/service/plugin/internal/runtime"
1316
"lina-core/pkg/pluginbridge"
@@ -48,6 +51,29 @@ func CreateTestPluginDir(t *testing.T, pluginID string) string {
4851

4952
sourcePlugin := pluginhost.NewSourcePlugin(pluginID)
5053
sourcePlugin.Assets().UseEmbeddedFiles(os.DirFS(pluginDir))
54+
sourcePlugin.Cron().RegisterCron(
55+
pluginhost.ExtensionPointCronRegister,
56+
pluginhost.CallbackExecutionModeBlocking,
57+
func(ctx context.Context, registrar pluginhost.CronRegistrar) error {
58+
hostServices := registrar.HostServices()
59+
if hostServices == nil || hostServices.Config() == nil {
60+
return gerror.New("test source plugin cron requires host config service")
61+
}
62+
if _, err := hostServices.Config().Exists(ctx, "monitor.interval"); err != nil {
63+
return err
64+
}
65+
return registrar.AddWithMetadata(
66+
ctx,
67+
"# * * * * *",
68+
pluginID+"-test-source-fixture-cron",
69+
"Test Source Fixture Cron",
70+
"Verifies source-plugin cron collection receives host services.",
71+
func(ctx context.Context) error {
72+
return nil
73+
},
74+
)
75+
},
76+
)
5177
cleanup, err := pluginhost.RegisterSourcePluginForTest(sourcePlugin)
5278
if err != nil {
5379
t.Fatalf("failed to register source plugin fixture %s: %v", pluginID, err)

apps/lina-core/internal/service/plugin/internal/testutil/testutil_services.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import (
1616
"lina-core/internal/service/plugin/internal/lifecycle"
1717
"lina-core/internal/service/plugin/internal/openapi"
1818
"lina-core/internal/service/plugin/internal/runtime"
19+
"lina-core/pkg/pluginhost"
20+
pluginserviceconfig "lina-core/pkg/pluginservice/config"
21+
"lina-core/pkg/pluginservice/contract"
1922
)
2023

2124
// Services groups the wired plugin sub-services used by package-level tests.
@@ -82,6 +85,7 @@ func NewServices() *Services {
8285

8386
integrationSvc.SetBizCtxProvider(&bizCtxAdapter{svc: bizCtxProvider})
8487
integrationSvc.SetDynamicCronExecutor(runtimeSvc)
88+
integrationSvc.SetHostServices(newTestHostServices())
8589
integrationSvc.SetTopologyProvider(topology)
8690

8791
runtimeSvc.SetMenuManager(integrationSvc)
@@ -102,6 +106,58 @@ func NewServices() *Services {
102106
}
103107
}
104108

109+
// testHostServices publishes the minimal host service directory needed by
110+
// source-plugin callbacks exercised in plugin service tests.
111+
type testHostServices struct {
112+
// configSvc exposes read-only GoFrame configuration to source plugins.
113+
configSvc contract.ConfigService
114+
}
115+
116+
// Ensure testHostServices satisfies the source-plugin host service directory.
117+
var _ pluginhost.HostServices = (*testHostServices)(nil)
118+
119+
// newTestHostServices creates a host service directory for integration tests.
120+
func newTestHostServices() pluginhost.HostServices {
121+
return &testHostServices{
122+
configSvc: pluginserviceconfig.New(),
123+
}
124+
}
125+
126+
// APIDoc returns no apidoc service for plugin integration tests.
127+
func (s *testHostServices) APIDoc() contract.APIDocService { return nil }
128+
129+
// Auth returns no auth service for plugin integration tests.
130+
func (s *testHostServices) Auth() contract.AuthService { return nil }
131+
132+
// BizCtx returns no bizctx service for plugin integration tests.
133+
func (s *testHostServices) BizCtx() contract.BizCtxService { return nil }
134+
135+
// Config returns the test host configuration service.
136+
func (s *testHostServices) Config() contract.ConfigService {
137+
if s == nil {
138+
return nil
139+
}
140+
return s.configSvc
141+
}
142+
143+
// I18n returns no i18n service for plugin integration tests.
144+
func (s *testHostServices) I18n() contract.I18nService { return nil }
145+
146+
// Notify returns no notification service for plugin integration tests.
147+
func (s *testHostServices) Notify() contract.NotifyService { return nil }
148+
149+
// PluginState returns no plugin-state service for plugin integration tests.
150+
func (s *testHostServices) PluginState() contract.PluginStateService { return nil }
151+
152+
// Route returns no route service for plugin integration tests.
153+
func (s *testHostServices) Route() contract.RouteService { return nil }
154+
155+
// Session returns no session service for plugin integration tests.
156+
func (s *testHostServices) Session() contract.SessionService { return nil }
157+
158+
// TenantFilter returns no tenant-filter service for plugin integration tests.
159+
func (s *testHostServices) TenantFilter() contract.TenantFilterService { return nil }
160+
105161
// jwtConfigAdapter exposes config service JWT settings through the runtime test seam.
106162
type jwtConfigAdapter struct {
107163
// svc provides JWT runtime configuration.

hack/tools/linactl/command_ops.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,18 @@ func goWorkspaceModules(ctx context.Context, a *app) ([]string, error) {
240240
var modules []string
241241
for _, line := range lines {
242242
line = strings.TrimSpace(line)
243-
if line != "" {
243+
if line != "" && !isGeneratedOfficialPluginAggregateModule(a.root, line) {
244244
modules = append(modules, line)
245245
}
246246
}
247247
return modules, nil
248248
}
249+
250+
// isGeneratedOfficialPluginAggregateModule reports whether a module directory
251+
// is the ignored aggregate bridge used only to satisfy host blank imports.
252+
func isGeneratedOfficialPluginAggregateModule(root string, moduleDir string) bool {
253+
if strings.TrimSpace(moduleDir) == "" {
254+
return false
255+
}
256+
return filepath.Clean(moduleDir) == filepath.Clean(officialPluginAggregateModuleDir(root))
257+
}

0 commit comments

Comments
 (0)