/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { EventEmitter } from 'node:events';
import type { Request, RequestHandler, Response } from 'express';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { runWithContext } from '../../core/context.js';
import { isHttpError } from '../../core/http/HttpError.js';
import { configureRateLimit } from '../../core/http/middleware/rateLimit.js';
import { DriverController } from './DriverController.js';
// The subscription requirement a driver declares per method, exercised
// through `#handleCall` the same way the concurrent test does: a synthetic
// driver carrying only the field under test, plus a stub metering service
// that answers with a fixed plan.
const captureCallHandler = (controller: DriverController): RequestHandler => {
let handler: RequestHandler | undefined;
const fakeRouter = {
post: (path: string, _opts: unknown, h: RequestHandler) => {
if (path === '/call') handler = h;
return fakeRouter;
},
get: () => fakeRouter,
use: () => fakeRouter,
};
controller.registerRoutes(
fakeRouter as unknown as Parameters<
typeof controller.registerRoutes
>[0],
);
if (!handler) throw new Error('failed to capture POST /call handler');
return handler;
};
class StubRes extends EventEmitter {
statusCode = 200;
body: unknown = undefined;
status(code: number) {
this.statusCode = code;
return this;
}
json(body: unknown) {
this.body = body;
return this;
}
setHeader() {
return this;
}
type() {
return this;
}
send() {
return this;
}
}
const makeReq = (method: string, tag: string): Request =>
({
body: { interface: 'test-iface', method },
actor: { user: { uuid: `u-${tag}`, username: 'u' } },
headers: { 'user-agent': tag },
query: {},
ip: '127.0.0.1',
socket: { remoteAddress: '127.0.0.1' },
}) as unknown as Request;
const syntheticDriver = {
driverInterface: 'test-iface',
driverName: 'test-driver',
isDefault: true,
requireSubscription: {
methods: { paid: true, premium: ['business', 'pro'] },
},
onServerStart() {},
onServerPrepareShutdown() {},
onServerShutdown() {},
free: async () => 'free-ok',
paid: async () => 'paid-ok',
premium: async () => 'premium-ok',
};
const buildController = (plan: string | null) => {
const clients = { alarm: { create: () => {} } };
const services = plan
? {
metering: {
getActorSubscription: vi.fn().mockResolvedValue({ id: plan }),
},
}
: {};
return new DriverController(
{} as any,
clients as any,
{} as any,
services as any,
{ syntheticDriver } as any,
);
};
const call = (plan: string | null, method: string, tag: string) => {
const handler = captureCallHandler(buildController(plan));
const res = new StubRes();
return runWithContext({}, () =>
handler(makeReq(method, tag), res as unknown as Response, () => {}),
).then(() => res);
};
const expect402 = (promise: Promise) =>
expect(promise).rejects.toSatisfy(
(e) =>
isHttpError(e) &&
(e as { statusCode: number }).statusCode === 402 &&
(e as { legacyCode?: string }).legacyCode ===
'subscription_required',
);
describe('DriverController — per-method subscription requirement', () => {
beforeEach(() => {
configureRateLimit();
});
it('leaves a method that declares nothing open to a free account', async () => {
const res = await call('user_free', 'free', 'open');
expect(res.body).toMatchObject({ success: true, result: 'free-ok' });
});
it('rejects a free account on a method that requires a subscription', async () => {
await expect402(call('user_free', 'paid', 'paid-free'));
});
it('admits a subscriber on that same method', async () => {
const res = await call('basic', 'paid', 'paid-sub');
expect(res.body).toMatchObject({ success: true, result: 'paid-ok' });
});
it('honours a per-method plan allowlist', async () => {
await expect402(call('basic', 'premium', 'premium-basic'));
const res = await call('pro', 'premium', 'premium-pro');
expect(res.body).toMatchObject({ success: true, result: 'premium-ok' });
});
it('enforces nothing on a deployment with no metering service', async () => {
const res = await call(null, 'paid', 'no-metering');
expect(res.body).toMatchObject({ success: true, result: 'paid-ok' });
});
});