11import { Test , type TestingModule } from '@nestjs/testing' ;
2+ import { NotFoundException } from '@nestjs/common' ;
23import { PoliciesService } from './policies.service' ;
34import { AttachmentsService } from '../attachments/attachments.service' ;
45import { PolicyPdfRendererService } from '../trust-portal/policy-pdf-renderer.service' ;
@@ -8,10 +9,18 @@ jest.mock('@db', () => ({
89 policy : {
910 findMany : jest . fn ( ) ,
1011 findFirst : jest . fn ( ) ,
12+ findUnique : jest . fn ( ) ,
13+ update : jest . fn ( ) ,
14+ } ,
15+ policyVersion : {
16+ findUnique : jest . fn ( ) ,
17+ findFirst : jest . fn ( ) ,
18+ create : jest . fn ( ) ,
1119 update : jest . fn ( ) ,
1220 } ,
1321 member : {
1422 findMany : jest . fn ( ) ,
23+ findFirst : jest . fn ( ) ,
1524 } ,
1625 auditLog : {
1726 createMany : jest . fn ( ) ,
@@ -46,8 +55,19 @@ jest.mock('../utils/compliance-filters', () => ({
4655// eslint-disable-next-line @typescript-eslint/no-require-imports
4756const { db } = require ( '@db' ) as {
4857 db : {
49- policy : { findMany : jest . Mock ; findFirst : jest . Mock ; update : jest . Mock } ;
50- member : { findMany : jest . Mock } ;
58+ policy : {
59+ findMany : jest . Mock ;
60+ findFirst : jest . Mock ;
61+ findUnique : jest . Mock ;
62+ update : jest . Mock ;
63+ } ;
64+ policyVersion : {
65+ findUnique : jest . Mock ;
66+ findFirst : jest . Mock ;
67+ create : jest . Mock ;
68+ update : jest . Mock ;
69+ } ;
70+ member : { findMany : jest . Mock ; findFirst : jest . Mock } ;
5171 auditLog : { createMany : jest . Mock } ;
5272 $transaction : jest . Mock ;
5373 } ;
@@ -61,12 +81,17 @@ const { filterComplianceMembers: mockedFilterComplianceMembers } = require('../u
6181describe ( 'PoliciesService' , ( ) => {
6282 let service : PoliciesService ;
6383
84+ const mockAttachmentsService = {
85+ copyPolicyVersionPdf : jest . fn ( ) ,
86+ deletePolicyVersionPdf : jest . fn ( ) ,
87+ } ;
88+
6489 beforeEach ( async ( ) => {
6590 jest . clearAllMocks ( ) ;
6691 const module : TestingModule = await Test . createTestingModule ( {
6792 providers : [
6893 PoliciesService ,
69- { provide : AttachmentsService , useValue : { } } ,
94+ { provide : AttachmentsService , useValue : mockAttachmentsService } ,
7095 { provide : PolicyPdfRendererService , useValue : { } } ,
7196 ] ,
7297 } ) . compile ( ) ;
@@ -216,4 +241,114 @@ describe('PoliciesService', () => {
216241 ] ) ;
217242 } ) ;
218243 } ) ;
244+
245+ describe ( 'createVersion' , ( ) => {
246+ const organizationId = 'org_123' ;
247+ const policyId = 'pol_1' ;
248+ const userId = 'usr_1' ;
249+
250+ const setupHappyPath = ( {
251+ policyContent,
252+ currentVersionContent,
253+ policyPdfUrl,
254+ currentVersionPdfUrl,
255+ } : {
256+ policyContent : unknown [ ] ;
257+ currentVersionContent : unknown [ ] | null ;
258+ policyPdfUrl : string | null ;
259+ currentVersionPdfUrl : string | null ;
260+ } ) => {
261+ db . member . findFirst . mockResolvedValue ( { id : 'mem_1' } ) ;
262+ db . policy . findUnique . mockResolvedValue ( {
263+ id : policyId ,
264+ organizationId,
265+ content : policyContent ,
266+ pdfUrl : policyPdfUrl ,
267+ currentVersion : currentVersionContent
268+ ? {
269+ id : 'pv_1' ,
270+ content : currentVersionContent ,
271+ pdfUrl : currentVersionPdfUrl ,
272+ }
273+ : null ,
274+ versions : [ ] ,
275+ } ) ;
276+ db . $transaction . mockImplementation ( async ( cb : ( tx : unknown ) => unknown ) =>
277+ cb ( {
278+ policyVersion : {
279+ findFirst : jest . fn ( ) . mockResolvedValue ( { version : 1 } ) ,
280+ create : jest . fn ( ) . mockResolvedValue ( { id : 'pv_2' } ) ,
281+ } ,
282+ } ) ,
283+ ) ;
284+ } ;
285+
286+ it ( 'creates a version when editor content is empty but a PDF exists' , async ( ) => {
287+ setupHappyPath ( {
288+ policyContent : [ ] ,
289+ currentVersionContent : [ ] ,
290+ policyPdfUrl : 's3://bucket/policy.pdf' ,
291+ currentVersionPdfUrl : 's3://bucket/policy.pdf' ,
292+ } ) ;
293+ mockAttachmentsService . copyPolicyVersionPdf . mockResolvedValue (
294+ 's3://bucket/new.pdf' ,
295+ ) ;
296+
297+ const result = await service . createVersion (
298+ policyId ,
299+ organizationId ,
300+ { } ,
301+ userId ,
302+ ) ;
303+
304+ expect ( result ) . toEqual ( { versionId : 'pv_2' , version : 2 } ) ;
305+ expect ( mockAttachmentsService . copyPolicyVersionPdf ) . toHaveBeenCalled ( ) ;
306+ } ) ;
307+
308+ it ( 'creates a version when both editor content is empty and no PDF exists' , async ( ) => {
309+ setupHappyPath ( {
310+ policyContent : [ ] ,
311+ currentVersionContent : [ ] ,
312+ policyPdfUrl : null ,
313+ currentVersionPdfUrl : null ,
314+ } ) ;
315+
316+ const result = await service . createVersion (
317+ policyId ,
318+ organizationId ,
319+ { } ,
320+ userId ,
321+ ) ;
322+
323+ expect ( result ) . toEqual ( { versionId : 'pv_2' , version : 2 } ) ;
324+ expect ( mockAttachmentsService . copyPolicyVersionPdf ) . not . toHaveBeenCalled ( ) ;
325+ } ) ;
326+
327+ it ( 'creates a version with non-empty editor content' , async ( ) => {
328+ setupHappyPath ( {
329+ policyContent : [ { type : 'paragraph' } ] ,
330+ currentVersionContent : [ { type : 'paragraph' } ] ,
331+ policyPdfUrl : null ,
332+ currentVersionPdfUrl : null ,
333+ } ) ;
334+
335+ const result = await service . createVersion (
336+ policyId ,
337+ organizationId ,
338+ { } ,
339+ userId ,
340+ ) ;
341+
342+ expect ( result ) . toEqual ( { versionId : 'pv_2' , version : 2 } ) ;
343+ } ) ;
344+
345+ it ( 'throws NotFound when the policy does not exist' , async ( ) => {
346+ db . member . findFirst . mockResolvedValue ( { id : 'mem_1' } ) ;
347+ db . policy . findUnique . mockResolvedValue ( null ) ;
348+
349+ await expect (
350+ service . createVersion ( policyId , organizationId , { } , userId ) ,
351+ ) . rejects . toBeInstanceOf ( NotFoundException ) ;
352+ } ) ;
353+ } ) ;
219354} ) ;
0 commit comments