mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 14:33:19 +00:00
feat(fleet): move bulk Remote OTA updates to Community tier (#1151)
Drops `requirePaid` from `POST /api/fleet/update-all` so Community admins can dispatch bulk node updates. Per-node OTA was already Community- reachable (admin-only); this completes the move so the full Remote OTA surface ships at Community. Frontend mirrors the backend: removes `canBulkUpdate` from NodeUpdatesSheet so the "Update all (N)" affordance is purely data- driven on `updatableRemoteCount > 0`. Docs realigned to drop fence-spec and Skipper-only phrasing on the Update all bulk action: - features/licensing.mdx: Community line now lists Remote OTA (per-node and Update all); Skipper Fleet Actions parenthetical drops "bulk update all" - features/remote-updates.mdx: Note rewritten to role-only requirement - features/fleet-view.mdx: Update all (n) bullet drops the tier clause - features/overview.mdx: Fleet View and Remote updates blurbs drop the Skipper/Admiral fences - operations/upgrade.mdx: Note rephrased without naming tiers Test coverage: - fleet.test.ts: tier-gating spec flipped to assert Community access - fleet-pilot-update.test.ts: bulk-OTA dispatch suite now spies tier to Community so it doubles as a regression guard
This commit is contained in:
@@ -184,15 +184,15 @@ describe('POST /api/fleet/nodes/:nodeId/update (pilot-agent)', () => {
|
||||
});
|
||||
|
||||
describe('POST /api/fleet/update-all (pilot-agent mixed fleet)', () => {
|
||||
// /update-all is requirePaid; spy the license tier so the test DB does not
|
||||
// need a real activation row.
|
||||
function mockPaidTier() {
|
||||
vi.spyOn(LicenseService.getInstance(), 'getTier').mockReturnValue('paid');
|
||||
vi.spyOn(LicenseService.getInstance(), 'getVariant').mockReturnValue('admiral');
|
||||
// Bulk OTA is admin-only and runs at every tier; spy the tier to Community
|
||||
// so this suite doubles as a regression guard that the gate has not been
|
||||
// re-introduced.
|
||||
function mockCommunityTier() {
|
||||
vi.spyOn(LicenseService.getInstance(), 'getTier').mockReturnValue('community');
|
||||
}
|
||||
|
||||
it('includes the pilot node in the candidate set and dispatches through its target', async () => {
|
||||
mockPaidTier();
|
||||
mockCommunityTier();
|
||||
mockTargetForPilot();
|
||||
mockMeta(META_ONLINE_OUTDATED);
|
||||
const postedUrls: string[] = [];
|
||||
@@ -213,7 +213,7 @@ describe('POST /api/fleet/update-all (pilot-agent mixed fleet)', () => {
|
||||
});
|
||||
|
||||
it('skips remotes whose target resolves to null and never calls /api/system/update on them', async () => {
|
||||
mockPaidTier();
|
||||
mockCommunityTier();
|
||||
mockTargetUnreachable();
|
||||
// /update-all also calls api.github.com to compute the compare target;
|
||||
// pin the assertion to the route's own dispatch surface.
|
||||
|
||||
@@ -157,13 +157,12 @@ describe('GET /api/fleet/overview', () => {
|
||||
describe('Fleet tier gating', () => {
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it('POST /api/fleet/update-all returns 403 on community tier (bulk update is Skipper+)', async () => {
|
||||
it('POST /api/fleet/update-all is accessible on community tier', async () => {
|
||||
mockTier('community');
|
||||
const res = await request(app)
|
||||
.post('/api/fleet/update-all')
|
||||
.set('Authorization', authHeader);
|
||||
expect(res.status).toBe(403);
|
||||
expect(res.body.code).toBe('PAID_REQUIRED');
|
||||
expect(res.body.code).not.toBe('PAID_REQUIRED');
|
||||
});
|
||||
|
||||
it('GET /api/fleet/update-status is accessible on community tier', async () => {
|
||||
|
||||
@@ -937,7 +937,6 @@ fleetRouter.post('/nodes/:nodeId/update', authMiddleware, async (req: Request, r
|
||||
});
|
||||
|
||||
fleetRouter.post('/update-all', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
if (!requirePaid(req, res)) return;
|
||||
if (!requireAdmin(req, res)) return;
|
||||
try {
|
||||
const db = DatabaseService.getInstance();
|
||||
|
||||
Reference in New Issue
Block a user