@@ -8,6 +8,7 @@ import { AppService } from '../src/app.service';
88
99import { ScraperController } from '../src/scraper/scraper.controller' ;
1010import { ScraperService , ScrapeResult } from '../src/scraper/scraper.service' ;
11+ import { AiController } from '../src/ai/ai.controller' ;
1112import { AiService } from '../src/ai/ai.service' ;
1213import { FeedController } from '../src/feed/feed.controller' ;
1314import { FeedService , FeedResponse } from '../src/feed/feed.service' ;
@@ -131,6 +132,68 @@ describe('API E2E (isolated modules)', () => {
131132 } ) ;
132133 } ) ;
133134
135+ describe ( 'Ops-only AI process (AiController + ScraperKeyGuard)' , ( ) => {
136+ let aiService : jest . Mocked < AiService > ;
137+
138+ beforeEach ( async ( ) => {
139+ aiService = {
140+ processUnsummarized : jest . fn ( ) ,
141+ } as any ;
142+
143+ const moduleFixture : TestingModule = await Test . createTestingModule ( {
144+ controllers : [ AiController ] ,
145+ providers : [ { provide : AiService , useValue : aiService } ] ,
146+ } ) . compile ( ) ;
147+
148+ app = moduleFixture . createNestApplication ( ) ;
149+ app . useGlobalPipes (
150+ new ValidationPipe ( { whitelist : true , transform : true } ) ,
151+ ) ;
152+ await app . init ( ) ;
153+ } ) ;
154+
155+ afterEach ( async ( ) => {
156+ await app . close ( ) ;
157+ } ) ;
158+
159+ it ( 'POST /ai/process requires SCRAPER_API_KEY (rejects missing, JWT, wrong key)' , async ( ) => {
160+ const origKey = process . env . SCRAPER_API_KEY ;
161+ process . env . SCRAPER_API_KEY = 'test-scraper-key-xyz' ;
162+
163+ ( aiService . processUnsummarized as jest . Mock ) . mockResolvedValue ( {
164+ processed : 3 ,
165+ failed : 0 ,
166+ } ) ;
167+
168+ // no auth
169+ await request ( app . getHttpServer ( ) ) . post ( '/ai/process' ) . expect ( 401 ) ;
170+
171+ // user JWT must not work (this is ops-only, not end-user)
172+ const { authHeader } = await getTestAuthContextForE2E ( ) ;
173+ await request ( app . getHttpServer ( ) )
174+ . post ( '/ai/process' )
175+ . set ( 'Authorization' , authHeader )
176+ . expect ( 401 ) ;
177+
178+ // wrong scraper key
179+ await request ( app . getHttpServer ( ) )
180+ . post ( '/ai/process' )
181+ . set ( 'Authorization' , 'Bearer wrong-key' )
182+ . expect ( 401 ) ;
183+
184+ // valid scraper key
185+ await request ( app . getHttpServer ( ) )
186+ . post ( '/ai/process' )
187+ . set ( 'Authorization' , 'Bearer test-scraper-key-xyz' )
188+ . expect ( 201 )
189+ . expect ( { processed : 3 , failed : 0 } ) ;
190+
191+ expect ( aiService . processUnsummarized ) . toHaveBeenCalledTimes ( 1 ) ;
192+
193+ process . env . SCRAPER_API_KEY = origKey ;
194+ } ) ;
195+ } ) ;
196+
134197 describe ( 'Protected feed (JwtAuthGuard + FeedController)' , ( ) => {
135198 let feedService : jest . Mocked < FeedService > ;
136199
0 commit comments