feat: T4b/T4c wave 9 - phenology reports, autopilot A4-A10, honey ledger, Telegram delivery
- Phenology reports API with corrections and revokes; recompute per apiary via PhenologyRecomputeRequested, allowBackward for corrections; season changes are announced in the morning digest (changes since the previous digest, each once) - Season autopilot A4-A10 with per-apiary sync (Apiary before Colony locks) and WinterCheckRecorded - Honey batch ledger at runtime: initial amount, BOTTLING bookings, manual movements, one gram rounding, untracked bottlings never book - Telegram delivery: planning, Berlin delivery times incl. DST, quiet hours, permission re-check and stale skip at send time, rate limits, cleanup of old deliveries - ARCHITECTURE.md: assumptions 47-50 and updated sections 11.x Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
1971b2d9c4
commit
16cc557462
74 files changed
+9634
-340
No files matched your search
@@ -0,0 +1,79 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { App } from 'supertest/types';
|
||||
import { AppModule } from '../../src/app.module';
|
||||
import { configureApp } from '../../src/app.config';
|
||||
import { THROTTLER_NAMESPACE } from '../../src/common/throttler/prisma-throttler.storage';
|
||||
import { TelegramApiError } from '../../src/telegram/telegram-api-error';
|
||||
import { TelegramClient } from '../../src/telegram/telegram-client';
|
||||
import { TelegramModule } from '../../src/telegram/telegram.module';
|
||||
|
||||
export interface SentMessage {
|
||||
chatId: bigint;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stands in for the Bot API: no network. Every sendMessage is recorded;
|
||||
* `failWith` scripts the next answers (an error is thrown once per entry,
|
||||
* in order, for the given chat or any chat).
|
||||
*/
|
||||
export class FakeTelegramClient {
|
||||
readonly sent: SentMessage[] = [];
|
||||
private readonly failures: { chatId: bigint | null; error: Error }[] = [];
|
||||
|
||||
isConfigured(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
failWith(statusCode: number, description: string, chatId?: bigint): void {
|
||||
this.failures.push({
|
||||
chatId: chatId ?? null,
|
||||
error: new TelegramApiError('sendMessage', statusCode, description, null),
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage(chatId: bigint, text: string): Promise<void> {
|
||||
const index = this.failures.findIndex(
|
||||
(failure) => failure.chatId === null || failure.chatId === chatId,
|
||||
);
|
||||
if (index >= 0) {
|
||||
const [failure] = this.failures.splice(index, 1);
|
||||
return Promise.reject(failure.error);
|
||||
}
|
||||
this.sent.push({ chatId, text });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
sentTo(chatId: bigint): SentMessage[] {
|
||||
return this.sent.filter((message) => message.chatId === chatId);
|
||||
}
|
||||
}
|
||||
|
||||
export interface TelegramTestApp {
|
||||
app: INestApplication<App>;
|
||||
telegram: FakeTelegramClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* The whole API plus TelegramModule, with the Bot API client replaced by a
|
||||
* FakeTelegramClient. The TelegramSender is taken from the app and driven
|
||||
* by the test (no worker loop runs).
|
||||
*/
|
||||
export async function createTelegramTestApp(): Promise<TelegramTestApp> {
|
||||
const telegram = new FakeTelegramClient();
|
||||
const moduleFixture = await Test.createTestingModule({
|
||||
imports: [AppModule, TelegramModule],
|
||||
})
|
||||
.overrideProvider(THROTTLER_NAMESPACE)
|
||||
.useValue(randomUUID())
|
||||
.overrideProvider(TelegramClient)
|
||||
.useValue(telegram)
|
||||
.compile();
|
||||
const app = moduleFixture.createNestApplication<NestExpressApplication>();
|
||||
configureApp(app);
|
||||
await app.init();
|
||||
return { app, telegram };
|
||||
}
|
||||
Reference in new issue
Block a user