@@ -3,6 +3,7 @@ package openapi_test
3
3
import (
4
4
"bytes"
5
5
"os"
6
+ "strings"
6
7
"testing"
7
8
8
9
"github.com/speakeasy-api/openapi/openapi"
@@ -120,3 +121,123 @@ func TestInline_SiblingDirectories_Success(t *testing.T) {
120
121
// Compare the actual output with expected output
121
122
assert .Equal (t , string (expectedBytes ), string (actualYAML ), "Inlined document should match expected output" )
122
123
}
124
+
125
+ func TestInline_AdditionalOperations_Success (t * testing.T ) {
126
+ t .Parallel ()
127
+
128
+ ctx := t .Context ()
129
+
130
+ // Load the input document with additionalOperations
131
+ inputFile , err := os .Open ("testdata/inline/additionaloperations_input.yaml" )
132
+ require .NoError (t , err )
133
+ defer inputFile .Close ()
134
+
135
+ inputDoc , validationErrs , err := openapi .Unmarshal (ctx , inputFile )
136
+ require .NoError (t , err )
137
+ require .Empty (t , validationErrs , "Input document should be valid" )
138
+
139
+ // Configure inlining options
140
+ opts := openapi.InlineOptions {
141
+ ResolveOptions : openapi.ResolveOptions {
142
+ RootDocument : inputDoc ,
143
+ TargetLocation : "testdata/inline/additionaloperations_input.yaml" ,
144
+ },
145
+ RemoveUnusedComponents : true ,
146
+ }
147
+
148
+ // Inline all references
149
+ err = openapi .Inline (ctx , inputDoc , opts )
150
+ require .NoError (t , err )
151
+
152
+ // Marshal the inlined document to YAML
153
+ var buf bytes.Buffer
154
+ err = openapi .Marshal (ctx , inputDoc , & buf )
155
+ require .NoError (t , err )
156
+ actualYAML := buf .String ()
157
+
158
+ // Verify that additionalOperations are preserved
159
+ assert .Contains (t , actualYAML , "additionalOperations:" , "additionalOperations should be preserved" )
160
+
161
+ // Verify that external references in additionalOperations were inlined
162
+ assert .NotContains (t , actualYAML , "$ref:" , "No references should remain after inlining" )
163
+ assert .NotContains (t , actualYAML , "external_custom_operations.yaml" , "No external file references should remain" )
164
+
165
+ // Verify that the COPY operation has inlined content
166
+ assert .Contains (t , actualYAML , "COPY:" , "COPY operation should be present" )
167
+ assert .Contains (t , actualYAML , "operationId: copyResource" , "COPY operation content should be inlined" )
168
+
169
+ // Verify that external parameter was inlined in COPY operation
170
+ copyOperationSection := extractAdditionalOperationSection (actualYAML , "COPY" )
171
+ assert .Contains (t , copyOperationSection , "name: destination" , "DestinationParam should be inlined" )
172
+ assert .Contains (t , copyOperationSection , "in: header" , "DestinationParam should be inlined" )
173
+
174
+ // Verify that external request body was inlined in COPY operation
175
+ assert .Contains (t , copyOperationSection , "source_path:" , "CopyRequest schema should be inlined" )
176
+ assert .Contains (t , copyOperationSection , "destination_path:" , "CopyRequest schema should be inlined" )
177
+
178
+ // Verify that the PURGE operation has inlined content
179
+ assert .Contains (t , actualYAML , "PURGE:" , "PURGE operation should be present" )
180
+ assert .Contains (t , actualYAML , "operationId: purgeResource" , "PURGE operation content should be inlined" )
181
+
182
+ // Verify that external parameter was inlined in PURGE operation
183
+ purgeOperationSection := extractAdditionalOperationSection (actualYAML , "PURGE" )
184
+ assert .Contains (t , purgeOperationSection , "name: X-Confirm-Purge" , "ConfirmationParam should be inlined" )
185
+ assert .Contains (t , purgeOperationSection , "pattern: ^CONFIRM-[A-Z0-9]{8}$" , "ConfirmationParam schema should be inlined" )
186
+
187
+ // Verify that the SYNC operation has inlined content
188
+ assert .Contains (t , actualYAML , "SYNC:" , "SYNC operation should be present" )
189
+ assert .Contains (t , actualYAML , "operationId: syncResource" , "SYNC operation content should be inlined" )
190
+
191
+ // Verify that external schemas were inlined in SYNC operation
192
+ syncOperationSection := extractAdditionalOperationSection (actualYAML , "SYNC" )
193
+ assert .Contains (t , syncOperationSection , "source:" , "SyncConfig schema should be inlined" )
194
+ assert .Contains (t , syncOperationSection , "destination:" , "SyncConfig schema should be inlined" )
195
+ assert .Contains (t , syncOperationSection , "sync_id:" , "SyncResult schema should be inlined" )
196
+ assert .Contains (t , syncOperationSection , "files_synced:" , "SyncResult schema should be inlined" )
197
+
198
+ // Verify that the BATCH operation has inlined content
199
+ assert .Contains (t , actualYAML , "BATCH:" , "BATCH operation should be present" )
200
+ assert .Contains (t , actualYAML , "operationId: batchProcess" , "BATCH operation content should be inlined" )
201
+
202
+ // Verify that nested external schemas were properly inlined
203
+ batchOperationSection := extractAdditionalOperationSection (actualYAML , "BATCH" )
204
+ assert .Contains (t , batchOperationSection , "parallel_execution:" , "BatchConfig schema should be inlined" )
205
+ assert .Contains (t , batchOperationSection , "batch_id:" , "BatchResult schema should be inlined" )
206
+ assert .Contains (t , batchOperationSection , "max_attempts:" , "RetryPolicy schema should be inlined" )
207
+
208
+ // Verify components section was removed (since RemoveUnusedComponents is true)
209
+ // Note: Some components might remain if they're still referenced from the main document
210
+ if ! assert .NotContains (t , actualYAML , "components:" , "Components section should be removed after inlining" ) {
211
+ // If components section exists, ensure it doesn't contain the external schemas
212
+ assert .NotContains (t , actualYAML , "ResourceMetadata:" , "External ResourceMetadata should not be in components after inlining" )
213
+ assert .NotContains (t , actualYAML , "SyncConfig:" , "External SyncConfig should not be in components after inlining" )
214
+ }
215
+ }
216
+
217
+ // Helper function to extract a specific additionalOperation section from YAML
218
+ func extractAdditionalOperationSection (yamlContent , operationName string ) string {
219
+ lines := strings .Split (yamlContent , "\n " )
220
+ var sectionLines []string
221
+ inTargetOperation := false
222
+ indentLevel := - 1
223
+
224
+ for _ , line := range lines {
225
+ if strings .Contains (line , operationName + ":" ) && strings .Contains (line , "additionalOperations" ) == false {
226
+ inTargetOperation = true
227
+ indentLevel = len (line ) - len (strings .TrimLeft (line , " " ))
228
+ sectionLines = append (sectionLines , line )
229
+ continue
230
+ }
231
+
232
+ if inTargetOperation {
233
+ currentIndent := len (line ) - len (strings .TrimLeft (line , " " ))
234
+ // If we hit a line at the same or lower indent level, we've left the operation
235
+ if strings .TrimSpace (line ) != "" && currentIndent <= indentLevel {
236
+ break
237
+ }
238
+ sectionLines = append (sectionLines , line )
239
+ }
240
+ }
241
+
242
+ return strings .Join (sectionLines , "\n " )
243
+ }
0 commit comments