@@ -81,6 +81,8 @@ import type { ServiceClient } from '../../src/types';
8181import { OpportunityJob } from '../../src/entity/opportunities/OpportunityJob' ;
8282import * as brokkrCommon from '../../src/common/brokkr' ;
8383import { randomUUID } from 'node:crypto' ;
84+ import { updateRecruiterSubscriptionFlags } from '../../src/common' ;
85+ import { SubscriptionStatus } from '../../src/common/plus' ;
8486
8587// Mock Slack WebClient
8688const mockConversationsCreate = jest . fn ( ) ;
@@ -5055,6 +5057,25 @@ describe('mutation updateOpportunityState', () => {
50555057 type : OpportunityUserType . Recruiter ,
50565058 } ) ;
50575059
5060+ await con . getRepository ( Organization ) . update (
5061+ {
5062+ id : organizationsFixture [ 0 ] . id ,
5063+ } ,
5064+ {
5065+ recruiterSubscriptionFlags :
5066+ updateRecruiterSubscriptionFlags < Organization > ( {
5067+ subscriptionId : 'sub_test' ,
5068+ status : SubscriptionStatus . Active ,
5069+ items : [
5070+ {
5071+ priceId : 'test' ,
5072+ quantity : 1 ,
5073+ } ,
5074+ ] ,
5075+ } ) ,
5076+ } ,
5077+ ) ;
5078+
50585079 await testMutationErrorCode (
50595080 client ,
50605081 {
@@ -5094,6 +5115,25 @@ describe('mutation updateOpportunityState', () => {
50945115
50955116 const opportunityId = opportunitiesFixture [ 3 ] . id ;
50965117
5118+ await con . getRepository ( Organization ) . update (
5119+ {
5120+ id : opportunitiesFixture [ 3 ] . organizationId ! ,
5121+ } ,
5122+ {
5123+ recruiterSubscriptionFlags :
5124+ updateRecruiterSubscriptionFlags < Organization > ( {
5125+ subscriptionId : 'sub_test' ,
5126+ status : SubscriptionStatus . Active ,
5127+ items : [
5128+ {
5129+ priceId : 'test' ,
5130+ quantity : 1 ,
5131+ } ,
5132+ ] ,
5133+ } ) ,
5134+ } ,
5135+ ) ;
5136+
50975137 await con . getRepository ( OpportunityUser ) . save ( {
50985138 opportunityId,
50995139 userId : '1' ,
@@ -5191,6 +5231,168 @@ describe('mutation updateOpportunityState', () => {
51915231 'Opportunity must have an organization assigned' ,
51925232 ) ;
51935233 } ) ;
5234+
5235+ it ( 'should update state to CLOSED state' , async ( ) => {
5236+ loggedUser = '1' ;
5237+
5238+ const opportunity = await con . getRepository ( OpportunityJob ) . save ( {
5239+ title : 'Test' ,
5240+ tldr : 'Test' ,
5241+ state : OpportunityState . LIVE ,
5242+ organizationId : organizationsFixture [ 0 ] . id ,
5243+ } ) ;
5244+
5245+ await con . getRepository ( OpportunityUser ) . save ( {
5246+ opportunityId : opportunity . id ,
5247+ userId : '1' ,
5248+ type : OpportunityUserType . Recruiter ,
5249+ } ) ;
5250+
5251+ await con . getRepository ( Organization ) . update (
5252+ {
5253+ id : organizationsFixture [ 0 ] . id ,
5254+ } ,
5255+ {
5256+ recruiterSubscriptionFlags :
5257+ updateRecruiterSubscriptionFlags < Organization > ( {
5258+ subscriptionId : 'sub_test' ,
5259+ status : SubscriptionStatus . Active ,
5260+ items : [
5261+ {
5262+ priceId : 'test' ,
5263+ quantity : 1 ,
5264+ } ,
5265+ ] ,
5266+ } ) ,
5267+ } ,
5268+ ) ;
5269+
5270+ const res = await client . mutate ( MUTATION , {
5271+ variables : { id : opportunity . id , state : OpportunityState . CLOSED } ,
5272+ } ) ;
5273+
5274+ expect ( res . errors ) . toBeFalsy ( ) ;
5275+
5276+ const after = await con
5277+ . getRepository ( Opportunity )
5278+ . findOneByOrFail ( { id : opportunity . id } ) ;
5279+ expect ( after . state ) . toBe ( OpportunityState . CLOSED ) ;
5280+ } ) ;
5281+
5282+ it ( 'should throw conflict on CLOSED transition when subscription is missing' , async ( ) => {
5283+ loggedUser = '1' ;
5284+
5285+ const opportunity = await con . getRepository ( OpportunityJob ) . save ( {
5286+ title : 'Test' ,
5287+ tldr : 'Test' ,
5288+ state : OpportunityState . LIVE ,
5289+ organizationId : organizationsFixture [ 0 ] . id ,
5290+ } ) ;
5291+
5292+ await con . getRepository ( OpportunityUser ) . save ( {
5293+ opportunityId : opportunity . id ,
5294+ userId : '1' ,
5295+ type : OpportunityUserType . Recruiter ,
5296+ } ) ;
5297+
5298+ await testMutationErrorCode (
5299+ client ,
5300+ {
5301+ mutation : MUTATION ,
5302+ variables : { id : opportunity . id , state : OpportunityState . CLOSED } ,
5303+ } ,
5304+ 'CONFLICT' ,
5305+ 'Opportunity subscription not found' ,
5306+ ) ;
5307+ } ) ;
5308+
5309+ it ( 'should throw conflict on LIVE transition when subscription is not active yet' , async ( ) => {
5310+ loggedUser = '1' ;
5311+
5312+ const opportunity = await con . getRepository ( OpportunityJob ) . save ( {
5313+ title : 'Test' ,
5314+ tldr : 'Test' ,
5315+ state : OpportunityState . DRAFT ,
5316+ organizationId : organizationsFixture [ 0 ] . id ,
5317+ } ) ;
5318+
5319+ await con . getRepository ( OpportunityUser ) . save ( {
5320+ opportunityId : opportunity . id ,
5321+ userId : '1' ,
5322+ type : OpportunityUserType . Recruiter ,
5323+ } ) ;
5324+
5325+ await testMutationErrorCode (
5326+ client ,
5327+ {
5328+ mutation : MUTATION ,
5329+ variables : { id : opportunity . id , state : OpportunityState . LIVE } ,
5330+ } ,
5331+ 'CONFLICT' ,
5332+ 'Opportunity subscription is not active yet, make sure your payment was processed in full. Contact support if the issue persists.' ,
5333+ ) ;
5334+ } ) ;
5335+
5336+ it ( 'should throw payment required on LIVE transition when no more allowed seats' , async ( ) => {
5337+ loggedUser = '1' ;
5338+
5339+ const opportunityId = opportunitiesFixture [ 3 ] . id ;
5340+
5341+ await con . getRepository ( Organization ) . update (
5342+ {
5343+ id : opportunitiesFixture [ 3 ] . organizationId ! ,
5344+ } ,
5345+ {
5346+ recruiterSubscriptionFlags :
5347+ updateRecruiterSubscriptionFlags < Organization > ( {
5348+ subscriptionId : 'sub_test' ,
5349+ status : SubscriptionStatus . Active ,
5350+ items : [ ] ,
5351+ } ) ,
5352+ } ,
5353+ ) ;
5354+
5355+ await con . getRepository ( OpportunityUser ) . save ( {
5356+ opportunityId,
5357+ userId : '1' ,
5358+ type : OpportunityUserType . Recruiter ,
5359+ } ) ;
5360+
5361+ await con . getRepository ( OpportunityKeyword ) . save ( {
5362+ opportunityId,
5363+ keyword : 'typescript' ,
5364+ } ) ;
5365+ await con . getRepository ( QuestionScreening ) . save ( {
5366+ opportunityId,
5367+ title : 'Tell us about a recent project' ,
5368+ questionOrder : 0 ,
5369+ } ) ;
5370+ await con . getRepository ( Opportunity ) . update (
5371+ { id : opportunityId } ,
5372+ {
5373+ content : {
5374+ overview : { content : 'Overview content' , html : '' } ,
5375+ responsibilities : { content : 'Responsibilities content' , html : '' } ,
5376+ requirements : { content : 'Requirements content' , html : '' } ,
5377+ } ,
5378+ } ,
5379+ ) ;
5380+
5381+ const before = await con
5382+ . getRepository ( Opportunity )
5383+ . findOneByOrFail ( { id : opportunityId } ) ;
5384+ expect ( before . state ) . toBe ( OpportunityState . DRAFT ) ;
5385+
5386+ await testMutationErrorCode (
5387+ client ,
5388+ {
5389+ mutation : MUTATION ,
5390+ variables : { id : opportunityId , state : OpportunityState . LIVE } ,
5391+ } ,
5392+ 'PAYMENT_REQUIRED' ,
5393+ 'Your subscription allows for 0 live opportunities. Please upgrade your subscription to add more or pause other live opportunities.' ,
5394+ ) ;
5395+ } ) ;
51945396} ) ;
51955397
51965398describe ( 'mutation parseOpportunity' , ( ) => {
0 commit comments