@@ -8,12 +8,13 @@ import {
88 executeOciComputeOperation ,
99 resolveOciComputeRetryToken ,
1010} from '@/lib/internal/oci-compute/operations'
11- import { ociComputeSchemas , type OciComputeOperation } from '@/lib/internal/oci-compute/schema'
11+ import { type OciComputeOperation , ociComputeSchemas } from '@/lib/internal/oci-compute/schema'
1212
1313const auth = { oauthCredential : 'credential' , region : 'us-ashburn-1' }
1414function harness ( data : unknown = { } , status = 200 , headers : Record < string , string > = { } ) {
1515 const request = vi . fn ( ) . mockResolvedValue ( {
16- status, headers : { 'opc-request-id' : 'request' , ...headers } ,
16+ status,
17+ headers : { 'opc-request-id' : 'request' , ...headers } ,
1718 body : new TextEncoder ( ) . encode ( status === 204 ? '' : JSON . stringify ( data ) ) ,
1819 } )
1920 const client = {
@@ -24,7 +25,9 @@ function harness(data: unknown = {}, status = 200, headers: Record<string, strin
2425}
2526async function run ( operation : OciComputeOperation , input : Record < string , unknown > , h = harness ( ) ) {
2627 const result = await executeOciComputeOperation (
27- h . client , operation , ociComputeSchemas [ operation ] . parse ( { ...auth , ...input } )
28+ h . client ,
29+ operation ,
30+ ociComputeSchemas [ operation ] . parse ( { ...auth , ...input } )
2831 )
2932 return { ...h , result }
3033}
@@ -33,21 +36,69 @@ function body(request: OciRequest) {
3336}
3437
3538describe ( 'OCI Compute requests' , ( ) => {
39+ it ( 'preserves token and payload across re-entry of a lifecycle invocation' , async ( ) => {
40+ const input = {
41+ instanceId : 'instance' , action : 'STOP' ,
42+ deliveryIdentity : { executionId : 'execution' , blockId : 'block' , invocationId : 'call' } ,
43+ }
44+ const first = await run ( 'instance_action' , input )
45+ const second = await run ( 'instance_action' , input )
46+ expect ( first . request . mock . calls [ 0 ] [ 0 ] . retry ) . toEqual ( second . request . mock . calls [ 0 ] [ 0 ] . retry )
47+ expect ( body ( first . request . mock . calls [ 0 ] [ 0 ] ) ) . toEqual ( body ( second . request . mock . calls [ 0 ] [ 0 ] ) )
48+ } )
49+
50+ it ( 'builds configuration creation and deferred launch overrides distinctly' , async ( ) => {
51+ const created = await run ( 'create_instance_configuration' , {
52+ compartmentId : 'destination' , configurationSource : 'INSTANCE' , instanceId : 'source' ,
53+ } )
54+ expect ( body ( created . request . mock . calls [ 0 ] [ 0 ] ) ) . toEqual ( {
55+ compartmentId : 'destination' , source : 'INSTANCE' , instanceId : 'source' ,
56+ } )
57+ const launched = await run ( 'launch_instance_configuration' , {
58+ instanceConfigurationId : 'configuration' ,
59+ instanceDetails : { instanceType : 'compute' , blockVolumes : [ { volumeId : 'existing' } ] } ,
60+ } )
61+ expect ( body ( launched . request . mock . calls [ 0 ] [ 0 ] ) ) . toEqual ( {
62+ instanceType : 'compute' , blockVolumes : [ { volumeId : 'existing' } ] ,
63+ } )
64+ } )
65+
66+ it ( 'returns optional work headers and sends explicit member-detachment choices' , async ( ) => {
67+ const { request, result } = await run ( 'detach_instance_pool_instance' , {
68+ instancePoolId : 'pool' , instanceId : 'instance' , isAutoTerminate : false , isDecrementSize : false ,
69+ retryToken : 'member-delivery' ,
70+ } , harness ( undefined , 202 , { 'opc-work-request-id' : 'work' } ) )
71+ expect ( body ( request . mock . calls [ 0 ] [ 0 ] ) ) . toEqual ( {
72+ instanceId : 'instance' , isAutoTerminate : false , isDecrementSize : false ,
73+ } )
74+ expect ( result . output ) . toMatchObject ( { workRequestId : 'work' , retryToken : 'member-delivery' } )
75+ } )
76+
3677 it . each ( [
3778 [ 'image' , { imageId : 'image' } , { sourceType : 'image' , imageId : 'image' } ] ,
38- [ 'imageFilter' , { imageFilter : { compartmentId : 'images' } } , {
39- sourceType : 'image' , instanceSourceImageFilterDetails : { compartmentId : 'images' } ,
40- } ] ,
79+ [
80+ 'imageFilter' ,
81+ { imageFilter : { compartmentId : 'images' } } ,
82+ {
83+ sourceType : 'image' ,
84+ instanceSourceImageFilterDetails : { compartmentId : 'images' } ,
85+ } ,
86+ ] ,
4187 [ 'bootVolume' , { bootVolumeId : 'boot' } , { sourceType : 'bootVolume' , bootVolumeId : 'boot' } ] ,
4288 ] ) ( 'builds the %s launch source and retains its token' , async ( sourceMode , source , expected ) => {
4389 const { request, result } = await run ( 'launch_instance' , {
44- compartmentId : 'compartment' , availabilityDomain : 'AD' , shape : 'shape' ,
90+ compartmentId : 'compartment' ,
91+ availabilityDomain : 'AD' ,
92+ shape : 'shape' ,
4593 createVnicDetails : { subnetId : 'subnet' , assignPublicIp : false } ,
46- sourceMode, ...source , retryToken : 'explicit-token' ,
94+ sourceMode,
95+ ...source ,
96+ retryToken : 'explicit-token' ,
4797 } )
4898 expect ( body ( request . mock . calls [ 0 ] [ 0 ] ) ) . toMatchObject ( { sourceDetails : expected } )
4999 expect ( request . mock . calls [ 0 ] [ 0 ] ) . toMatchObject ( {
50- method : 'POST' , encodedPath : '/20160918/instances/' ,
100+ method : 'POST' ,
101+ encodedPath : '/20160918/instances/' ,
51102 retry : { kind : 'tokenized' , maxAttempts : 2 , retryToken : 'explicit-token' } ,
52103 maxResponseBytes : 2_000_000 ,
53104 } )
@@ -57,45 +108,84 @@ describe('OCI Compute requests', () => {
57108
58109 it . each ( [
59110 [ 'START' , { } , undefined ] ,
60- [ 'RESET' , { allowDenseRebootMigration : false } , { actionType : 'reset' , allowDenseRebootMigration : false } ] ,
61- [ 'SOFTRESET' , { allowDenseRebootMigration : true } , { actionType : 'softreset' , allowDenseRebootMigration : true } ] ,
62- [ 'REBOOTMIGRATE' , { deleteLocalStorage : false } , { actionType : 'rebootMigrate' , deleteLocalStorage : false } ] ,
111+ [
112+ 'RESET' ,
113+ { allowDenseRebootMigration : false } ,
114+ { actionType : 'reset' , allowDenseRebootMigration : false } ,
115+ ] ,
116+ [
117+ 'SOFTRESET' ,
118+ { allowDenseRebootMigration : true } ,
119+ { actionType : 'softreset' , allowDenseRebootMigration : true } ,
120+ ] ,
121+ [
122+ 'REBOOTMIGRATE' ,
123+ { deleteLocalStorage : false } ,
124+ { actionType : 'rebootMigrate' , deleteLocalStorage : false } ,
125+ ] ,
63126 ] ) ( 'discriminates the %s lifecycle body' , async ( action , fields , expected ) => {
64- const { request } = await run ( 'instance_action' , { instanceId : 'instance' , action, ...fields , ifMatch : 'etag' } )
127+ const { request } = await run ( 'instance_action' , {
128+ instanceId : 'instance' ,
129+ action,
130+ ...fields ,
131+ ifMatch : 'etag' ,
132+ } )
65133 expect ( body ( request . mock . calls [ 0 ] [ 0 ] ) ) . toEqual ( expected )
66134 expect ( request . mock . calls [ 0 ] [ 0 ] ) . toMatchObject ( {
67- queryPairs : [ [ 'action' , action ] ] , headers : { 'if-match' : 'etag' } ,
135+ queryPairs : [ [ 'action' , action ] ] ,
136+ headers : { 'if-match' : 'etag' } ,
68137 } )
69- expect ( request . mock . calls [ 0 ] [ 0 ] . retry ) . toBeUndefined ( )
138+ expect ( request . mock . calls [ 0 ] [ 0 ] . retry ) . toMatchObject ( { kind : 'tokenized' , maxAttempts : 2 } )
70139 } )
71140
72141 it ( 'uses a pool action path and a single attempt' , async ( ) => {
73- const { request } = await run ( 'instance_pool_action' , { instancePoolId : 'pool' , action : 'SOFTSTOP' } )
142+ const { request } = await run ( 'instance_pool_action' , {
143+ instancePoolId : 'pool' ,
144+ action : 'SOFTSTOP' ,
145+ } )
74146 expect ( request . mock . calls [ 0 ] [ 0 ] ) . toMatchObject ( {
75- method : 'POST' , encodedPath : '/20160918/instancePools/pool/actions/softstop' ,
147+ method : 'POST' ,
148+ encodedPath : '/20160918/instancePools/pool/actions/softstop' ,
76149 } )
77150 expect ( body ( request . mock . calls [ 0 ] [ 0 ] ) ) . toBeUndefined ( )
78- expect ( request . mock . calls [ 0 ] [ 0 ] . retry ) . toBeUndefined ( )
151+ expect ( request . mock . calls [ 0 ] [ 0 ] . retry ) . toMatchObject ( { kind : 'tokenized' , maxAttempts : 2 } )
79152 } )
80153
81154 it ( 'terminates once with explicit preservation choices and accepts an empty response' , async ( ) => {
82- const { request, result } = await run ( 'terminate_instance' , {
83- instanceId : 'instance' , preserveBootVolume : false , preserveDataVolumesCreatedAtLaunch : true ,
84- ifMatch : 'etag' ,
85- } , harness ( undefined , 204 ) )
155+ const { request, result } = await run (
156+ 'terminate_instance' ,
157+ {
158+ instanceId : 'instance' ,
159+ preserveBootVolume : false ,
160+ preserveDataVolumesCreatedAtLaunch : true ,
161+ ifMatch : 'etag' ,
162+ } ,
163+ harness ( undefined , 204 )
164+ )
86165 expect ( result . success ) . toBe ( true )
87166 expect ( request . mock . calls [ 0 ] [ 0 ] ) . toMatchObject ( {
88- method : 'DELETE' , headers : { 'if-match' : 'etag' } ,
89- queryPairs : [ [ 'preserveBootVolume' , 'false' ] , [ 'preserveDataVolumesCreatedAtLaunch' , 'true' ] ] ,
167+ method : 'DELETE' ,
168+ headers : { 'if-match' : 'etag' } ,
169+ queryPairs : [
170+ [ 'preserveBootVolume' , 'false' ] ,
171+ [ 'preserveDataVolumesCreatedAtLaunch' , 'true' ] ,
172+ ] ,
90173 } )
91174 expect ( request . mock . calls [ 0 ] [ 0 ] . retry ) . toBeUndefined ( )
92175 expect ( request ) . toHaveBeenCalledTimes ( 1 )
93176 } )
94177
95178 it ( 'retains pagination on an empty page and encodes opaque IDs once' , async ( ) => {
96- const { request, result } = await run ( 'list_instance_pool_instances' , {
97- instancePoolId : 'pool/a' , compartmentId : 'compartment' , page : 'opaque+/=' , limit : 50 ,
98- } , harness ( [ ] , 200 , { 'opc-next-page' : 'next' } ) )
179+ const { request, result } = await run (
180+ 'list_instance_pool_instances' ,
181+ {
182+ instancePoolId : 'pool/a' ,
183+ compartmentId : 'compartment' ,
184+ page : 'opaque+/=' ,
185+ limit : 50 ,
186+ } ,
187+ harness ( [ ] , 200 , { 'opc-next-page' : 'next' } )
188+ )
99189 expect ( result . output ) . toMatchObject ( { poolInstances : [ ] , nextPage : 'next' } )
100190 expect ( request . mock . calls [ 0 ] [ 0 ] ) . toMatchObject ( {
101191 encodedPath : '/20160918/instancePools/pool%2Fa/instances' ,
@@ -108,7 +198,11 @@ describe('OCI Compute requests', () => {
108198 const h = harness ( )
109199 h . request . mockRejectedValue ( new OciClientError ( 'request_failed' ) )
110200 const { result } = await run ( 'terminate_instance' , { instanceId : 'instance' } , h )
111- expect ( result ) . toMatchObject ( { success : false , retryable : false , output : { outcome : 'unknown' } } )
201+ expect ( result ) . toMatchObject ( {
202+ success : false ,
203+ retryable : false ,
204+ output : { outcome : 'unknown' } ,
205+ } )
112206 expect ( h . request ) . toHaveBeenCalledTimes ( 1 )
113207 } )
114208
0 commit comments