@api-client/core 0.18.11 → 0.18.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/browser.d.ts +0 -3
- package/build/src/browser.d.ts.map +1 -1
- package/build/src/browser.js +0 -3
- package/build/src/browser.js.map +1 -1
- package/build/src/index.d.ts +2 -5
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +2 -5
- package/build/src/index.js.map +1 -1
- package/build/src/lib/logging/DefaultLogger.d.ts +14 -0
- package/build/src/lib/logging/DefaultLogger.d.ts.map +1 -1
- package/build/src/lib/logging/DefaultLogger.js +27 -0
- package/build/src/lib/logging/DefaultLogger.js.map +1 -1
- package/build/src/lib/logging/index.d.ts +4 -0
- package/build/src/lib/logging/index.d.ts.map +1 -0
- package/build/src/lib/logging/index.js +10 -0
- package/build/src/lib/logging/index.js.map +1 -0
- package/build/src/modeling/DomainModel.d.ts.map +1 -1
- package/build/src/modeling/DomainModel.js +11 -4
- package/build/src/modeling/DomainModel.js.map +1 -1
- package/build/src/models/ClientCertificate.d.ts +1 -1
- package/build/src/models/ClientCertificate.js.map +1 -1
- package/build/src/models/RequestConfig.d.ts +1 -1
- package/build/src/models/RequestConfig.js.map +1 -1
- package/build/src/models/SerializableError.d.ts +1 -1
- package/build/src/models/SerializableError.d.ts.map +1 -1
- package/build/src/models/SerializableError.js.map +1 -1
- package/build/src/proxy/RequestProxy.d.ts.map +1 -1
- package/build/src/proxy/RequestProxy.js +2 -2
- package/build/src/proxy/RequestProxy.js.map +1 -1
- package/build/src/runtime/http-engine/CoreEngine.d.ts +218 -139
- package/build/src/runtime/http-engine/CoreEngine.d.ts.map +1 -1
- package/build/src/runtime/http-engine/CoreEngine.js +716 -870
- package/build/src/runtime/http-engine/CoreEngine.js.map +1 -1
- package/build/src/runtime/http-engine/PayloadSupport.d.ts.map +1 -1
- package/build/src/runtime/http-engine/PayloadSupport.js +2 -1
- package/build/src/runtime/http-engine/PayloadSupport.js.map +1 -1
- package/build/src/runtime/http-engine/auth/AuthManager.d.ts +73 -0
- package/build/src/runtime/http-engine/auth/AuthManager.d.ts.map +1 -0
- package/build/src/runtime/http-engine/auth/AuthManager.js +186 -0
- package/build/src/runtime/http-engine/auth/AuthManager.js.map +1 -0
- package/build/src/runtime/http-engine/auth/index.d.ts +2 -0
- package/build/src/runtime/http-engine/auth/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/auth/index.js +2 -0
- package/build/src/runtime/http-engine/auth/index.js.map +1 -0
- package/build/src/runtime/http-engine/certificates/CertificateManager.d.ts +11 -0
- package/build/src/runtime/http-engine/certificates/CertificateManager.d.ts.map +1 -0
- package/build/src/runtime/http-engine/certificates/CertificateManager.js +76 -0
- package/build/src/runtime/http-engine/certificates/CertificateManager.js.map +1 -0
- package/build/src/runtime/http-engine/certificates/index.d.ts +2 -0
- package/build/src/runtime/http-engine/certificates/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/certificates/index.js +2 -0
- package/build/src/runtime/http-engine/certificates/index.js.map +1 -0
- package/build/src/runtime/http-engine/compression/CompressionManager.d.ts +25 -0
- package/build/src/runtime/http-engine/compression/CompressionManager.d.ts.map +1 -0
- package/build/src/runtime/http-engine/compression/CompressionManager.js +89 -0
- package/build/src/runtime/http-engine/compression/CompressionManager.js.map +1 -0
- package/build/src/runtime/http-engine/compression/index.d.ts +2 -0
- package/build/src/runtime/http-engine/compression/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/compression/index.js +2 -0
- package/build/src/runtime/http-engine/compression/index.js.map +1 -0
- package/build/src/runtime/http-engine/connections/ConnectionManager.d.ts +57 -0
- package/build/src/runtime/http-engine/connections/ConnectionManager.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/ConnectionManager.js +78 -0
- package/build/src/runtime/http-engine/connections/ConnectionManager.js.map +1 -0
- package/build/src/runtime/http-engine/connections/DigestAuthHandler.d.ts +70 -0
- package/build/src/runtime/http-engine/connections/DigestAuthHandler.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/DigestAuthHandler.js +184 -0
- package/build/src/runtime/http-engine/connections/DigestAuthHandler.js.map +1 -0
- package/build/src/runtime/http-engine/connections/DirectConnection.d.ts +22 -0
- package/build/src/runtime/http-engine/connections/DirectConnection.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/DirectConnection.js +105 -0
- package/build/src/runtime/http-engine/connections/DirectConnection.js.map +1 -0
- package/build/src/runtime/http-engine/connections/ProxyAuthHandler.d.ts +60 -0
- package/build/src/runtime/http-engine/connections/ProxyAuthHandler.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/ProxyAuthHandler.js +138 -0
- package/build/src/runtime/http-engine/connections/ProxyAuthHandler.js.map +1 -0
- package/build/src/runtime/http-engine/connections/ProxyConnection.d.ts +14 -0
- package/build/src/runtime/http-engine/connections/ProxyConnection.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/ProxyConnection.js +47 -0
- package/build/src/runtime/http-engine/connections/ProxyConnection.js.map +1 -0
- package/build/src/runtime/http-engine/connections/TunnelConnection.d.ts +13 -0
- package/build/src/runtime/http-engine/connections/TunnelConnection.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/TunnelConnection.js +175 -0
- package/build/src/runtime/http-engine/connections/TunnelConnection.js.map +1 -0
- package/build/src/runtime/http-engine/connections/index.d.ts +7 -0
- package/build/src/runtime/http-engine/connections/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/connections/index.js +7 -0
- package/build/src/runtime/http-engine/connections/index.js.map +1 -0
- package/build/src/runtime/http-engine/constants.d.ts +69 -0
- package/build/src/runtime/http-engine/constants.d.ts.map +1 -0
- package/build/src/runtime/http-engine/constants.js +90 -0
- package/build/src/runtime/http-engine/constants.js.map +1 -0
- package/build/src/runtime/http-engine/cookies/CookieProcessor.d.ts +5 -0
- package/build/src/runtime/http-engine/cookies/CookieProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/cookies/CookieProcessor.js +20 -0
- package/build/src/runtime/http-engine/cookies/CookieProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/cookies/index.d.ts +2 -0
- package/build/src/runtime/http-engine/cookies/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/cookies/index.js +2 -0
- package/build/src/runtime/http-engine/cookies/index.js.map +1 -0
- package/build/src/runtime/http-engine/errors/HttpEngineErrors.d.ts +156 -0
- package/build/src/runtime/http-engine/errors/HttpEngineErrors.d.ts.map +1 -0
- package/build/src/runtime/http-engine/errors/HttpEngineErrors.js +227 -0
- package/build/src/runtime/http-engine/errors/HttpEngineErrors.js.map +1 -0
- package/build/src/runtime/http-engine/errors/index.d.ts +2 -0
- package/build/src/runtime/http-engine/errors/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/errors/index.js +2 -0
- package/build/src/runtime/http-engine/errors/index.js.map +1 -0
- package/build/src/runtime/http-engine/message/MessageBuilder.d.ts +66 -0
- package/build/src/runtime/http-engine/message/MessageBuilder.d.ts.map +1 -0
- package/build/src/runtime/http-engine/message/MessageBuilder.js +161 -0
- package/build/src/runtime/http-engine/message/MessageBuilder.js.map +1 -0
- package/build/src/runtime/http-engine/message/MessageProcessor.d.ts +27 -0
- package/build/src/runtime/http-engine/message/MessageProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/message/MessageProcessor.js +51 -0
- package/build/src/runtime/http-engine/message/MessageProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/message/index.d.ts +3 -0
- package/build/src/runtime/http-engine/message/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/message/index.js +3 -0
- package/build/src/runtime/http-engine/message/index.js.map +1 -0
- package/build/src/runtime/http-engine/ntlm/NtlmAuth.d.ts +2 -8
- package/build/src/runtime/http-engine/ntlm/NtlmAuth.d.ts.map +1 -1
- package/build/src/runtime/http-engine/ntlm/NtlmAuth.js +11 -5
- package/build/src/runtime/http-engine/ntlm/NtlmAuth.js.map +1 -1
- package/build/src/runtime/http-engine/ntlm/NtlmMessage.js +6 -6
- package/build/src/runtime/http-engine/ntlm/NtlmMessage.js.map +1 -1
- package/build/src/runtime/http-engine/parsers/BodyParser.d.ts +39 -0
- package/build/src/runtime/http-engine/parsers/BodyParser.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/BodyParser.js +145 -0
- package/build/src/runtime/http-engine/parsers/BodyParser.js.map +1 -0
- package/build/src/runtime/http-engine/parsers/HeadersParser.d.ts +29 -0
- package/build/src/runtime/http-engine/parsers/HeadersParser.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/HeadersParser.js +88 -0
- package/build/src/runtime/http-engine/parsers/HeadersParser.js.map +1 -0
- package/build/src/runtime/http-engine/parsers/HttpResponseParser.d.ts +91 -0
- package/build/src/runtime/http-engine/parsers/HttpResponseParser.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/HttpResponseParser.js +236 -0
- package/build/src/runtime/http-engine/parsers/HttpResponseParser.js.map +1 -0
- package/build/src/runtime/http-engine/parsers/StatusParser.d.ts +20 -0
- package/build/src/runtime/http-engine/parsers/StatusParser.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/StatusParser.js +51 -0
- package/build/src/runtime/http-engine/parsers/StatusParser.js.map +1 -0
- package/build/src/runtime/http-engine/parsers/index.d.ts +5 -0
- package/build/src/runtime/http-engine/parsers/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/parsers/index.js +5 -0
- package/build/src/runtime/http-engine/parsers/index.js.map +1 -0
- package/build/src/runtime/http-engine/response/ResponseProcessor.d.ts +22 -0
- package/build/src/runtime/http-engine/response/ResponseProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/response/ResponseProcessor.js +25 -0
- package/build/src/runtime/http-engine/response/ResponseProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/response/index.d.ts +2 -0
- package/build/src/runtime/http-engine/response/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/response/index.js +2 -0
- package/build/src/runtime/http-engine/response/index.js.map +1 -0
- package/build/src/runtime/http-engine/statistics/StatisticsProcessor.d.ts +7 -0
- package/build/src/runtime/http-engine/statistics/StatisticsProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/statistics/StatisticsProcessor.js +40 -0
- package/build/src/runtime/http-engine/statistics/StatisticsProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/statistics/index.d.ts +2 -0
- package/build/src/runtime/http-engine/statistics/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/statistics/index.js +2 -0
- package/build/src/runtime/http-engine/statistics/index.js.map +1 -0
- package/build/src/runtime/http-engine/url/UrlProcessor.d.ts +24 -0
- package/build/src/runtime/http-engine/url/UrlProcessor.d.ts.map +1 -0
- package/build/src/runtime/http-engine/url/UrlProcessor.js +50 -0
- package/build/src/runtime/http-engine/url/UrlProcessor.js.map +1 -0
- package/build/src/runtime/http-engine/url/index.d.ts +2 -0
- package/build/src/runtime/http-engine/url/index.d.ts.map +1 -0
- package/build/src/runtime/http-engine/url/index.js +2 -0
- package/build/src/runtime/http-engine/url/index.js.map +1 -0
- package/build/src/runtime/http-runner/HttpRequestRunner.d.ts +3 -3
- package/build/src/runtime/http-runner/HttpRequestRunner.d.ts.map +1 -1
- package/build/src/runtime/http-runner/HttpRequestRunner.js.map +1 -1
- package/build/src/runtime/node/InteropInterfaces.d.ts +3 -3
- package/build/src/runtime/node/InteropInterfaces.d.ts.map +1 -1
- package/build/src/runtime/node/InteropInterfaces.js.map +1 -1
- package/build/src/runtime/node/ProjectRequestRunner.d.ts +2 -2
- package/build/src/runtime/node/ProjectRequestRunner.d.ts.map +1 -1
- package/build/src/runtime/node/ProjectRequestRunner.js.map +1 -1
- package/build/src/runtime/node/ProjectRunner.d.ts.map +1 -1
- package/build/src/runtime/node/ProjectRunner.js +2 -2
- package/build/src/runtime/node/ProjectRunner.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +24 -24
- package/package.json +2 -2
- package/src/lib/logging/DefaultLogger.ts +32 -0
- package/src/modeling/DomainModel.ts +11 -4
- package/src/models/ClientCertificate.ts +1 -1
- package/src/models/RequestConfig.ts +1 -1
- package/src/models/SerializableError.ts +1 -1
- package/src/proxy/RequestProxy.ts +2 -2
- package/src/runtime/http-engine/CoreEngine.ts +858 -893
- package/src/runtime/http-engine/PayloadSupport.ts +2 -1
- package/src/runtime/http-engine/auth/AuthManager.ts +242 -0
- package/src/runtime/http-engine/certificates/CertificateManager.ts +74 -0
- package/src/runtime/http-engine/compression/CompressionManager.ts +99 -0
- package/src/runtime/http-engine/connections/ConnectionManager.ts +123 -0
- package/src/runtime/http-engine/connections/DigestAuthHandler.ts +238 -0
- package/src/runtime/http-engine/connections/DirectConnection.ts +134 -0
- package/src/runtime/http-engine/connections/ProxyAuthHandler.ts +179 -0
- package/src/runtime/http-engine/connections/ProxyConnection.ts +55 -0
- package/src/runtime/http-engine/connections/TunnelConnection.ts +192 -0
- package/src/runtime/http-engine/constants.ts +103 -0
- package/src/runtime/http-engine/cookies/CookieProcessor.ts +25 -0
- package/src/runtime/http-engine/errors/HttpEngineErrors.ts +319 -0
- package/src/runtime/http-engine/message/MessageBuilder.ts +201 -0
- package/src/runtime/http-engine/message/MessageProcessor.ts +73 -0
- package/src/runtime/http-engine/ntlm/NtlmAuth.ts +16 -13
- package/src/runtime/http-engine/ntlm/NtlmMessage.ts +6 -6
- package/src/runtime/http-engine/parsers/BodyParser.ts +171 -0
- package/src/runtime/http-engine/parsers/HeadersParser.ts +103 -0
- package/src/runtime/http-engine/parsers/HttpResponseParser.ts +280 -0
- package/src/runtime/http-engine/parsers/StatusParser.ts +69 -0
- package/src/runtime/http-engine/response/ResponseProcessor.ts +46 -0
- package/src/runtime/http-engine/statistics/StatisticsProcessor.ts +52 -0
- package/src/runtime/http-engine/url/UrlProcessor.ts +55 -0
- package/src/runtime/http-runner/HttpRequestRunner.ts +3 -3
- package/src/runtime/node/InteropInterfaces.ts +3 -3
- package/src/runtime/node/ProjectRequestRunner.ts +2 -2
- package/src/runtime/node/ProjectRunner.ts +2 -2
- package/tests/servers/ProxyServer.ts +32 -19
- package/tests/servers/express-routes/ApiEndpoint.ts +24 -0
- package/tests/servers/express-routes/BasicAuthRoute.ts +36 -0
- package/tests/servers/express-routes/BearerAuthRoute.ts +35 -0
- package/tests/servers/express-routes/NTLMRoute.ts +2 -3
- package/tests/servers/express-routes/PostApi.ts +15 -2
- package/tests/servers/express-routes/RedirectsApi.ts +12 -1
- package/tests/servers/express-routes/ResponsesApi.ts +1 -1
- package/tests/servers/express-routes/StreamApi.ts +19 -0
- package/tests/servers/oauth2mock/ServerMock.js +1 -1
- package/tests/unit/modeling/domain_model_entities.spec.ts +306 -1
- package/tests/unit/runtime/http-engine/HttpResponseParser.spec.ts +337 -0
- package/tests/unit/runtime/http-engine/abort.spec.ts +4 -5
- package/tests/unit/runtime/http-engine/auth.spec.ts +7 -58
- package/tests/unit/runtime/http-engine/certificates/CertificateManager.spec.ts +482 -0
- package/tests/unit/runtime/http-engine/certificates.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/compression/CompressionManager.spec.ts +498 -0
- package/tests/unit/runtime/http-engine/compression.spec.ts +3 -72
- package/tests/unit/runtime/http-engine/connections/ConnectionManager.spec.ts +379 -0
- package/tests/unit/runtime/http-engine/connections/DigestAuthHandler.spec.ts +164 -0
- package/tests/unit/runtime/http-engine/core_engine.spec.ts +561 -0
- package/tests/unit/runtime/http-engine/engine_statuses.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/events.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/headers.spec.ts +2 -88
- package/tests/unit/runtime/http-engine/hosts.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/http-get.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/http-post.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/logger.spec.ts +0 -8
- package/tests/unit/runtime/http-engine/message.spec.ts +2 -194
- package/tests/unit/runtime/http-engine/params.spec.ts +4 -4
- package/tests/unit/runtime/http-engine/proxy.spec.ts +15 -14
- package/tests/unit/runtime/http-engine/redirects.spec.ts +2 -2
- package/tests/unit/runtime/http-engine/responses.spec.ts +170 -277
- package/tests/unit/runtime/http-engine/timeout.spec.ts +3 -3
- package/tests/unit/runtime/http-engine/timings.spec.ts +2 -2
- package/tests/unit/runtime/proxy/HttpProjectProxy.spec.ts +25 -28
- package/tests/unit/runtime/runners/project_runner.spec.ts +2 -2
- package/tests/unit/runtime/runners/request_runner.spec.ts +2 -2
- package/build/src/runtime/http-engine/HttpEngine.d.ts +0 -311
- package/build/src/runtime/http-engine/HttpEngine.d.ts.map +0 -1
- package/build/src/runtime/http-engine/HttpEngine.js +0 -802
- package/build/src/runtime/http-engine/HttpEngine.js.map +0 -1
- package/src/runtime/http-engine/HttpEngine.ts +0 -952
- package/tests/unit/runtime/http-engine/connecting.spec.ts +0 -140
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { test } from '@japa/runner'
|
|
2
|
-
import { DataDomain, DomainEntity, DomainEntityKind } from '../../../src/index.js'
|
|
2
|
+
import { DataDomain, DomainEntity, DomainEntityKind, DomainModel } from '../../../src/index.js'
|
|
3
3
|
|
|
4
4
|
test.group('DomainModel.addEntity()', () => {
|
|
5
5
|
test('adds an entity to the model', ({ assert }) => {
|
|
@@ -406,3 +406,308 @@ test.group('DomainModel.hasEntities()', () => {
|
|
|
406
406
|
assert.isFalse(model.hasEntities())
|
|
407
407
|
})
|
|
408
408
|
})
|
|
409
|
+
|
|
410
|
+
test.group('DomainModel.attachEntity() - Race Condition Tests', () => {
|
|
411
|
+
test('simulate multiple entities moved to same target simultaneously', ({ assert }) => {
|
|
412
|
+
const dataDomain = new DataDomain()
|
|
413
|
+
const sourceModel1 = dataDomain.addModel({ key: 'source1' })
|
|
414
|
+
const sourceModel2 = dataDomain.addModel({ key: 'source2' })
|
|
415
|
+
const targetModel = dataDomain.addModel({ key: 'target' })
|
|
416
|
+
|
|
417
|
+
const entity1 = sourceModel1.addEntity({ key: 'entity1' })
|
|
418
|
+
const entity2 = sourceModel2.addEntity({ key: 'entity2' })
|
|
419
|
+
|
|
420
|
+
// Move multiple entities to the same target
|
|
421
|
+
targetModel.attachEntity(entity1.key)
|
|
422
|
+
targetModel.attachEntity(entity2.key)
|
|
423
|
+
|
|
424
|
+
// Verify no duplication in fields array
|
|
425
|
+
const entityFields = targetModel.fields.filter((f) => f.type === 'entity')
|
|
426
|
+
const entityKeys = entityFields.map((f) => f.key)
|
|
427
|
+
const uniqueKeys = [...new Set(entityKeys)]
|
|
428
|
+
assert.equal(entityFields.length, uniqueKeys.length, 'No duplicate entities in fields array')
|
|
429
|
+
assert.lengthOf(entityFields, 2, 'Should have exactly 2 entities')
|
|
430
|
+
assert.include(entityKeys, entity1.key, 'Should contain entity1')
|
|
431
|
+
assert.include(entityKeys, entity2.key, 'Should contain entity2')
|
|
432
|
+
|
|
433
|
+
// Verify graph consistency
|
|
434
|
+
assert.equal(dataDomain.graph.parent(entity1.key), targetModel.key, 'Entity1 parent should be target model')
|
|
435
|
+
assert.equal(dataDomain.graph.parent(entity2.key), targetModel.key, 'Entity2 parent should be target model')
|
|
436
|
+
|
|
437
|
+
// Verify entities removed from source models
|
|
438
|
+
assert.lengthOf(sourceModel1.fields, 0, 'Source1 should have no entities')
|
|
439
|
+
assert.lengthOf(sourceModel2.fields, 0, 'Source2 should have no entities')
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
test('simulate entity moved between models multiple times rapidly', ({ assert }) => {
|
|
443
|
+
const dataDomain = new DataDomain()
|
|
444
|
+
const model1 = dataDomain.addModel({ key: 'model1' })
|
|
445
|
+
const model2 = dataDomain.addModel({ key: 'model2' })
|
|
446
|
+
const model3 = dataDomain.addModel({ key: 'model3' })
|
|
447
|
+
|
|
448
|
+
const entity = model1.addEntity({ key: 'entity' })
|
|
449
|
+
|
|
450
|
+
// Simulate rapid moves
|
|
451
|
+
model2.attachEntity(entity.key)
|
|
452
|
+
model3.attachEntity(entity.key)
|
|
453
|
+
model1.attachEntity(entity.key)
|
|
454
|
+
model2.attachEntity(entity.key)
|
|
455
|
+
|
|
456
|
+
// Final state verification
|
|
457
|
+
assert.lengthOf(model2.fields, 1, 'Model2 should have 1 entity')
|
|
458
|
+
assert.lengthOf(model1.fields, 0, 'Model1 should have no entities')
|
|
459
|
+
assert.lengthOf(model3.fields, 0, 'Model3 should have no entities')
|
|
460
|
+
assert.equal(dataDomain.graph.parent(entity.key), model2.key, 'Entity parent should be model2')
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
test('simulate concurrent attachment of same entity to different targets', ({ assert }) => {
|
|
464
|
+
const dataDomain = new DataDomain()
|
|
465
|
+
const sourceModel = dataDomain.addModel({ key: 'source' })
|
|
466
|
+
const targetModel1 = dataDomain.addModel({ key: 'target1' })
|
|
467
|
+
const targetModel2 = dataDomain.addModel({ key: 'target2' })
|
|
468
|
+
|
|
469
|
+
const entity = sourceModel.addEntity({ key: 'entity' })
|
|
470
|
+
|
|
471
|
+
// First move should succeed
|
|
472
|
+
targetModel1.attachEntity(entity.key)
|
|
473
|
+
assert.equal(dataDomain.graph.parent(entity.key), targetModel1.key, 'Entity should be in target1')
|
|
474
|
+
assert.lengthOf(targetModel1.fields, 1, 'Target1 should have the entity')
|
|
475
|
+
assert.lengthOf(sourceModel.fields, 0, 'Source should be empty')
|
|
476
|
+
|
|
477
|
+
// Second move should move from target1 to target2
|
|
478
|
+
targetModel2.attachEntity(entity.key)
|
|
479
|
+
assert.equal(dataDomain.graph.parent(entity.key), targetModel2.key, 'Entity should be in target2')
|
|
480
|
+
assert.lengthOf(targetModel2.fields, 1, 'Target2 should have the entity')
|
|
481
|
+
assert.lengthOf(targetModel1.fields, 0, 'Target1 should be empty')
|
|
482
|
+
assert.lengthOf(sourceModel.fields, 0, 'Source should still be empty')
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
test('verify entity is not duplicated when moved from model with multiple entities', ({ assert }) => {
|
|
486
|
+
const dataDomain = new DataDomain()
|
|
487
|
+
const sourceModel = dataDomain.addModel({ key: 'source' })
|
|
488
|
+
const targetModel = dataDomain.addModel({ key: 'target' })
|
|
489
|
+
|
|
490
|
+
const entity1 = sourceModel.addEntity({ key: 'entity1' })
|
|
491
|
+
const entity2 = sourceModel.addEntity({ key: 'entity2' })
|
|
492
|
+
const entity3 = sourceModel.addEntity({ key: 'entity3' })
|
|
493
|
+
|
|
494
|
+
// Move middle entity
|
|
495
|
+
targetModel.attachEntity(entity2.key)
|
|
496
|
+
|
|
497
|
+
// Verify source model has 2 entities remaining
|
|
498
|
+
assert.lengthOf(sourceModel.fields, 2, 'Source should have 2 entities remaining')
|
|
499
|
+
const sourceKeys = sourceModel.fields.map((f) => f.key)
|
|
500
|
+
assert.include(sourceKeys, entity1.key, 'Should contain entity1')
|
|
501
|
+
assert.include(sourceKeys, entity3.key, 'Should contain entity3')
|
|
502
|
+
assert.notInclude(sourceKeys, entity2.key, 'Should not contain moved entity2')
|
|
503
|
+
|
|
504
|
+
// Verify target model has 1 entity
|
|
505
|
+
assert.lengthOf(targetModel.fields, 1, 'Target should have 1 entity')
|
|
506
|
+
assert.equal(targetModel.fields[0].key, entity2.key, 'Target should have entity2')
|
|
507
|
+
|
|
508
|
+
// Verify graph consistency
|
|
509
|
+
assert.equal(dataDomain.graph.parent(entity1.key), sourceModel.key, 'Entity1 parent should be source')
|
|
510
|
+
assert.equal(dataDomain.graph.parent(entity2.key), targetModel.key, 'Entity2 parent should be target')
|
|
511
|
+
assert.equal(dataDomain.graph.parent(entity3.key), sourceModel.key, 'Entity3 parent should be source')
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
test('verify field consistency when moving entities with properties', ({ assert }) => {
|
|
515
|
+
const dataDomain = new DataDomain()
|
|
516
|
+
const sourceModel = dataDomain.addModel({ key: 'source' })
|
|
517
|
+
const targetModel = dataDomain.addModel({ key: 'target' })
|
|
518
|
+
|
|
519
|
+
const entity = sourceModel.addEntity({ key: 'entity' })
|
|
520
|
+
entity.addProperty({ key: 'prop1', type: 'string' })
|
|
521
|
+
entity.addProperty({ key: 'prop2', type: 'number' })
|
|
522
|
+
|
|
523
|
+
// Verify initial state
|
|
524
|
+
assert.lengthOf(entity.fields, 2, 'Entity should have 2 properties')
|
|
525
|
+
|
|
526
|
+
// Move entity
|
|
527
|
+
targetModel.attachEntity(entity.key)
|
|
528
|
+
|
|
529
|
+
// Verify entity fields are preserved
|
|
530
|
+
assert.lengthOf(entity.fields, 2, 'Entity should still have 2 properties after move')
|
|
531
|
+
assert.equal(dataDomain.graph.parent(entity.key), targetModel.key, 'Entity parent should be target')
|
|
532
|
+
|
|
533
|
+
// Verify model fields
|
|
534
|
+
assert.lengthOf(sourceModel.fields, 0, 'Source model should be empty')
|
|
535
|
+
assert.lengthOf(targetModel.fields, 1, 'Target model should have 1 entity')
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
test('stress test - multiple entities moved in sequence to verify no field array corruption', ({ assert }) => {
|
|
539
|
+
const dataDomain = new DataDomain()
|
|
540
|
+
const sourceModels = []
|
|
541
|
+
const targetModel = dataDomain.addModel({ key: 'target' })
|
|
542
|
+
const entities = []
|
|
543
|
+
|
|
544
|
+
// Create 10 source models with 1 entity each
|
|
545
|
+
for (let i = 0; i < 10; i++) {
|
|
546
|
+
const sourceModel = dataDomain.addModel({ key: `source${i}` })
|
|
547
|
+
sourceModels.push(sourceModel)
|
|
548
|
+
const entity = sourceModel.addEntity({ key: `entity${i}` })
|
|
549
|
+
entities.push(entity)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Move all entities to target model
|
|
553
|
+
for (const entity of entities) {
|
|
554
|
+
targetModel.attachEntity(entity.key)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Verify target model state
|
|
558
|
+
assert.lengthOf(targetModel.fields, 10, 'Target should have 10 entities')
|
|
559
|
+
const targetKeys = targetModel.fields.map((f) => f.key).sort()
|
|
560
|
+
const expectedKeys = entities.map((e) => e.key).sort()
|
|
561
|
+
assert.deepEqual(targetKeys, expectedKeys, 'Target should have all expected entities')
|
|
562
|
+
|
|
563
|
+
// Verify no duplicates
|
|
564
|
+
const uniqueKeys = [...new Set(targetKeys)]
|
|
565
|
+
assert.equal(targetKeys.length, uniqueKeys.length, 'No duplicate keys in target')
|
|
566
|
+
|
|
567
|
+
// Verify source models are empty
|
|
568
|
+
for (const sourceModel of sourceModels) {
|
|
569
|
+
assert.lengthOf(sourceModel.fields, 0, `Source model ${sourceModel.key} should be empty`)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Verify graph consistency
|
|
573
|
+
for (const entity of entities) {
|
|
574
|
+
assert.equal(dataDomain.graph.parent(entity.key), targetModel.key, `Entity ${entity.key} parent should be target`)
|
|
575
|
+
}
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
test('verify detach-attach sequence atomicity', ({ assert }) => {
|
|
579
|
+
const dataDomain = new DataDomain()
|
|
580
|
+
const model1 = dataDomain.addModel({ key: 'model1' })
|
|
581
|
+
const model2 = dataDomain.addModel({ key: 'model2' })
|
|
582
|
+
|
|
583
|
+
const entity = model1.addEntity({ key: 'entity' })
|
|
584
|
+
|
|
585
|
+
// Spy on the detachEntity method to verify it's called
|
|
586
|
+
let detachCalled = false
|
|
587
|
+
const originalDetach = model1.detachEntity
|
|
588
|
+
model1.detachEntity = function (key: string) {
|
|
589
|
+
detachCalled = true
|
|
590
|
+
return originalDetach.call(this, key)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
model2.attachEntity(entity.key)
|
|
594
|
+
|
|
595
|
+
// Verify detach was called during attach
|
|
596
|
+
assert.isTrue(detachCalled, 'detachEntity should have been called on the source model')
|
|
597
|
+
|
|
598
|
+
// Verify final state
|
|
599
|
+
assert.lengthOf(model1.fields, 0, 'Model1 should be empty')
|
|
600
|
+
assert.lengthOf(model2.fields, 1, 'Model2 should have the entity')
|
|
601
|
+
assert.equal(dataDomain.graph.parent(entity.key), model2.key, 'Graph parent should be model2')
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
test('verify graph parent is updated before fields array check', ({ assert }) => {
|
|
605
|
+
const dataDomain = new DataDomain()
|
|
606
|
+
const model1 = dataDomain.addModel({ key: 'model1' })
|
|
607
|
+
const model2 = dataDomain.addModel({ key: 'model2' })
|
|
608
|
+
|
|
609
|
+
const entity = model1.addEntity({ key: 'entity' })
|
|
610
|
+
|
|
611
|
+
// Override setParent to track when it's called
|
|
612
|
+
const parentSetCalls: { nodeKey: string; parentKey?: string; timestamp: number }[] = []
|
|
613
|
+
const originalSetParent = dataDomain.graph.setParent
|
|
614
|
+
dataDomain.graph.setParent = function (nodeKey: string, parentKey?: string) {
|
|
615
|
+
parentSetCalls.push({ nodeKey, parentKey, timestamp: Date.now() })
|
|
616
|
+
return originalSetParent.call(this, nodeKey, parentKey)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Override fields.push to track when it's called
|
|
620
|
+
const fieldsPushCalls: { items: unknown[]; timestamp: number }[] = []
|
|
621
|
+
const originalPush = model2.fields.push
|
|
622
|
+
model2.fields.push = function (...items) {
|
|
623
|
+
fieldsPushCalls.push({ items: [...items], timestamp: Date.now() })
|
|
624
|
+
return originalPush.call(this, ...items)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
model2.attachEntity(entity.key)
|
|
628
|
+
|
|
629
|
+
// Verify the sequence - setParent should be called twice (detach + attach)
|
|
630
|
+
assert.lengthOf(parentSetCalls, 2, 'setParent should be called twice (detach + attach)')
|
|
631
|
+
assert.lengthOf(fieldsPushCalls, 1, 'fields.push should be called once')
|
|
632
|
+
|
|
633
|
+
// First call should clear the parent (detach)
|
|
634
|
+
assert.equal(parentSetCalls[0].nodeKey, entity.key, 'First call should be for entity key')
|
|
635
|
+
assert.isUndefined(parentSetCalls[0].parentKey, 'First call should clear parent')
|
|
636
|
+
|
|
637
|
+
// Second call should set the new parent (attach)
|
|
638
|
+
assert.equal(parentSetCalls[1].parentKey, model2.key, 'Second call should set parent to model2')
|
|
639
|
+
assert.equal(parentSetCalls[1].nodeKey, entity.key, 'Second call should be for entity key')
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
test('detect inconsistent graph parent - entity in fields but wrong graph parent', ({ assert }) => {
|
|
643
|
+
const dataDomain = new DataDomain()
|
|
644
|
+
const sourceModel = dataDomain.addModel({ key: 'source' })
|
|
645
|
+
const targetModel = dataDomain.addModel({ key: 'target' })
|
|
646
|
+
|
|
647
|
+
const entity = sourceModel.addEntity({ key: 'entity' })
|
|
648
|
+
|
|
649
|
+
// Simulate race condition where graph parent is not updated during attach
|
|
650
|
+
const originalSetParent = dataDomain.graph.setParent
|
|
651
|
+
let setParentCallCount = 0
|
|
652
|
+
dataDomain.graph.setParent = function (nodeKey: string, parentKey?: string) {
|
|
653
|
+
setParentCallCount++
|
|
654
|
+
// Skip the second setParent call (the attach operation)
|
|
655
|
+
if (setParentCallCount === 2) {
|
|
656
|
+
return this // Don't actually set the parent
|
|
657
|
+
}
|
|
658
|
+
return originalSetParent.call(this, nodeKey, parentKey)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
assert.throws(() => {
|
|
662
|
+
targetModel.attachEntity(entity.key)
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
// Restore original method
|
|
666
|
+
dataDomain.graph.setParent = originalSetParent
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
test('verify consistency during rapid sequential moves', ({ assert }) => {
|
|
670
|
+
const dataDomain = new DataDomain()
|
|
671
|
+
const models: DomainModel[] = []
|
|
672
|
+
|
|
673
|
+
// Create multiple models
|
|
674
|
+
for (let i = 0; i < 5; i++) {
|
|
675
|
+
models.push(dataDomain.addModel({ key: `model${i}` }))
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const entity = models[0].addEntity({ key: 'entity' })
|
|
679
|
+
|
|
680
|
+
// Perform rapid moves between models
|
|
681
|
+
for (let iteration = 0; iteration < 20; iteration++) {
|
|
682
|
+
const targetIndex = (iteration + 1) % models.length
|
|
683
|
+
models[targetIndex].attachEntity(entity.key)
|
|
684
|
+
|
|
685
|
+
// Verify consistency after each move
|
|
686
|
+
let entityCount = 0
|
|
687
|
+
let modelWithEntity = null
|
|
688
|
+
|
|
689
|
+
for (const model of models) {
|
|
690
|
+
const hasEntity = model.fields.some((f) => f.type === 'entity' && f.key === entity.key)
|
|
691
|
+
if (hasEntity) {
|
|
692
|
+
entityCount++
|
|
693
|
+
modelWithEntity = model
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Entity should exist in exactly one model
|
|
698
|
+
assert.equal(
|
|
699
|
+
entityCount,
|
|
700
|
+
1,
|
|
701
|
+
`Iteration ${iteration}: Entity should exist in exactly one model, found in ${entityCount} models`
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
// Graph parent should match the model that has the entity
|
|
705
|
+
const graphParent = dataDomain.graph.parent(entity.key)
|
|
706
|
+
assert.equal(
|
|
707
|
+
graphParent,
|
|
708
|
+
modelWithEntity?.key,
|
|
709
|
+
`Iteration ${iteration}: Graph parent should match model with entity`
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
})
|
|
713
|
+
})
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { createLogger } from '../../../../src/index.js'
|
|
3
|
+
import {
|
|
4
|
+
HttpResponseParser,
|
|
5
|
+
RequestState,
|
|
6
|
+
type ParserCallbacks,
|
|
7
|
+
} from '../../../../src/runtime/http-engine/parsers/HttpResponseParser.js'
|
|
8
|
+
|
|
9
|
+
const logger = createLogger()
|
|
10
|
+
|
|
11
|
+
test.group('HttpResponseParser', (group) => {
|
|
12
|
+
let parser: HttpResponseParser
|
|
13
|
+
let callbacks: ParserCallbacks
|
|
14
|
+
let statusParsed = false
|
|
15
|
+
let headersParsed = false
|
|
16
|
+
let bodyComplete = false
|
|
17
|
+
let errorOccurred = false
|
|
18
|
+
let aborted = false
|
|
19
|
+
let emittedEvents: { event: string; detail?: unknown }[] = []
|
|
20
|
+
|
|
21
|
+
group.each.setup(() => {
|
|
22
|
+
statusParsed = false
|
|
23
|
+
headersParsed = false
|
|
24
|
+
bodyComplete = false
|
|
25
|
+
errorOccurred = false
|
|
26
|
+
aborted = false
|
|
27
|
+
emittedEvents = []
|
|
28
|
+
|
|
29
|
+
callbacks = {
|
|
30
|
+
onStatusParsed: () => {
|
|
31
|
+
statusParsed = true
|
|
32
|
+
},
|
|
33
|
+
onHeadersParsed: () => {
|
|
34
|
+
headersParsed = true
|
|
35
|
+
},
|
|
36
|
+
onBodyComplete: () => {
|
|
37
|
+
bodyComplete = true
|
|
38
|
+
},
|
|
39
|
+
onError: () => {
|
|
40
|
+
errorOccurred = true
|
|
41
|
+
},
|
|
42
|
+
onAbort: () => {
|
|
43
|
+
aborted = true
|
|
44
|
+
},
|
|
45
|
+
emit: (event: string, detail?: unknown) => {
|
|
46
|
+
emittedEvents.push({ event, detail })
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
parser = new HttpResponseParser(logger, callbacks)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('parses status line correctly', ({ assert }) => {
|
|
54
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
55
|
+
parser.processData(statusData)
|
|
56
|
+
|
|
57
|
+
assert.isTrue(statusParsed)
|
|
58
|
+
assert.equal(parser.getState(), RequestState.Headers)
|
|
59
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
60
|
+
|
|
61
|
+
test('handles incomplete status line', ({ assert }) => {
|
|
62
|
+
const statusData = Buffer.from('HTTP/1.1 200')
|
|
63
|
+
parser.processData(statusData)
|
|
64
|
+
|
|
65
|
+
assert.isFalse(statusParsed)
|
|
66
|
+
assert.equal(parser.getState(), RequestState.Status)
|
|
67
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
68
|
+
|
|
69
|
+
test('handles malformed status line', ({ assert }) => {
|
|
70
|
+
const statusData = Buffer.from('Invalid Status Line\r\n')
|
|
71
|
+
parser.processData(statusData)
|
|
72
|
+
|
|
73
|
+
// StatusParser doesn't throw errors, it just returns invalid status codes
|
|
74
|
+
assert.isTrue(statusParsed)
|
|
75
|
+
assert.equal(parser.getState(), RequestState.Headers)
|
|
76
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
77
|
+
|
|
78
|
+
test('parses headers correctly', ({ assert }) => {
|
|
79
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
80
|
+
const headersData = Buffer.from('Content-Type: application/json\r\nContent-Length: 114\r\n\r\n')
|
|
81
|
+
|
|
82
|
+
parser.processData(statusData)
|
|
83
|
+
parser.processData(headersData)
|
|
84
|
+
|
|
85
|
+
assert.isTrue(statusParsed)
|
|
86
|
+
assert.isTrue(headersParsed)
|
|
87
|
+
assert.equal(parser.getState(), RequestState.Body)
|
|
88
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
89
|
+
|
|
90
|
+
test('handles partial headers', ({ assert }) => {
|
|
91
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
92
|
+
const partialHeaders = Buffer.from('Content-Type: application/json\r\n')
|
|
93
|
+
|
|
94
|
+
parser.processData(statusData)
|
|
95
|
+
parser.processData(partialHeaders)
|
|
96
|
+
|
|
97
|
+
assert.isTrue(statusParsed)
|
|
98
|
+
assert.isFalse(headersParsed)
|
|
99
|
+
assert.equal(parser.getState(), RequestState.Headers)
|
|
100
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
101
|
+
|
|
102
|
+
test('extracts content length from headers', ({ assert }) => {
|
|
103
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
104
|
+
const headersData = Buffer.from('Content-Length: 114\r\n\r\n')
|
|
105
|
+
|
|
106
|
+
parser.processData(statusData)
|
|
107
|
+
parser.processData(headersData)
|
|
108
|
+
|
|
109
|
+
const responseInfo = parser.getResponseInfo()
|
|
110
|
+
assert.equal(responseInfo.contentLength, 114)
|
|
111
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
112
|
+
|
|
113
|
+
test('detects chunked transfer encoding', ({ assert }) => {
|
|
114
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
115
|
+
const headersData = Buffer.from('Transfer-Encoding: chunked\r\n\r\n')
|
|
116
|
+
|
|
117
|
+
parser.processData(statusData)
|
|
118
|
+
parser.processData(headersData)
|
|
119
|
+
|
|
120
|
+
const responseInfo = parser.getResponseInfo()
|
|
121
|
+
assert.isTrue(responseInfo.chunked)
|
|
122
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
123
|
+
|
|
124
|
+
test('emits headersreceived event', ({ assert }) => {
|
|
125
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
126
|
+
const headersData = Buffer.from('Content-Type: application/json\r\n\r\n')
|
|
127
|
+
|
|
128
|
+
parser.processData(statusData)
|
|
129
|
+
parser.processData(headersData)
|
|
130
|
+
|
|
131
|
+
const headersEvent = emittedEvents.find((e) => e.event === 'headersreceived')
|
|
132
|
+
assert.isDefined(headersEvent)
|
|
133
|
+
assert.isDefined(headersEvent?.detail)
|
|
134
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
135
|
+
|
|
136
|
+
test('handles content-length body', ({ assert }) => {
|
|
137
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
138
|
+
const headersData = Buffer.from('Content-Length: 4\r\n\r\n')
|
|
139
|
+
const bodyData = Buffer.from('test')
|
|
140
|
+
|
|
141
|
+
parser.processData(statusData)
|
|
142
|
+
parser.processData(headersData)
|
|
143
|
+
parser.processData(bodyData)
|
|
144
|
+
|
|
145
|
+
assert.isTrue(bodyComplete)
|
|
146
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
147
|
+
|
|
148
|
+
test('handles chunked body', ({ assert }) => {
|
|
149
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
150
|
+
const headersData = Buffer.from('Transfer-Encoding: chunked\r\n\r\n')
|
|
151
|
+
const chunkedBody = Buffer.from('4\r\ntest\r\n0\r\n\r\n')
|
|
152
|
+
|
|
153
|
+
parser.processData(statusData)
|
|
154
|
+
parser.processData(headersData)
|
|
155
|
+
parser.processData(chunkedBody)
|
|
156
|
+
|
|
157
|
+
assert.isTrue(bodyComplete)
|
|
158
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
159
|
+
|
|
160
|
+
test('handles multiple chunks', ({ assert }) => {
|
|
161
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
162
|
+
const headersData = Buffer.from('Transfer-Encoding: chunked\r\n\r\n')
|
|
163
|
+
const chunkedBody = Buffer.from('6\r\ntest\r\n\r\n8\r\ntest1234\r\n0\r\n\r\n')
|
|
164
|
+
|
|
165
|
+
parser.processData(statusData)
|
|
166
|
+
parser.processData(headersData)
|
|
167
|
+
parser.processData(chunkedBody)
|
|
168
|
+
|
|
169
|
+
assert.isTrue(bodyComplete)
|
|
170
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
171
|
+
|
|
172
|
+
test('handles partial chunks', ({ assert }) => {
|
|
173
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
174
|
+
const headersData = Buffer.from('Transfer-Encoding: chunked\r\n\r\n')
|
|
175
|
+
|
|
176
|
+
parser.processData(statusData)
|
|
177
|
+
parser.processData(headersData)
|
|
178
|
+
|
|
179
|
+
// Send partial chunk
|
|
180
|
+
parser.processData(Buffer.from('6\r\nte'))
|
|
181
|
+
parser.processData(Buffer.from('st\r\n\r\n'))
|
|
182
|
+
parser.processData(Buffer.from('8\r\ntest'))
|
|
183
|
+
parser.processData(Buffer.from('1234\r\n0\r\n\r\n'))
|
|
184
|
+
|
|
185
|
+
assert.isTrue(bodyComplete)
|
|
186
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
187
|
+
|
|
188
|
+
test('handles 204 No Content without body', ({ assert }) => {
|
|
189
|
+
const statusData = Buffer.from('HTTP/1.1 204 No Content\r\n')
|
|
190
|
+
const headersData = Buffer.from('\r\n')
|
|
191
|
+
|
|
192
|
+
parser.processData(statusData)
|
|
193
|
+
parser.processData(headersData)
|
|
194
|
+
|
|
195
|
+
assert.isTrue(bodyComplete)
|
|
196
|
+
assert.equal(parser.getState(), RequestState.Done)
|
|
197
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
198
|
+
|
|
199
|
+
test('handles 304 Not Modified without body', ({ assert }) => {
|
|
200
|
+
const statusData = Buffer.from('HTTP/1.1 304 Not Modified\r\n')
|
|
201
|
+
const headersData = Buffer.from('\r\n')
|
|
202
|
+
|
|
203
|
+
parser.processData(statusData)
|
|
204
|
+
parser.processData(headersData)
|
|
205
|
+
|
|
206
|
+
assert.isTrue(bodyComplete)
|
|
207
|
+
assert.equal(parser.getState(), RequestState.Done)
|
|
208
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
209
|
+
|
|
210
|
+
test('handles informational responses without body', ({ assert }) => {
|
|
211
|
+
const statusData = Buffer.from('HTTP/1.1 100 Continue\r\n')
|
|
212
|
+
const headersData = Buffer.from('\r\n')
|
|
213
|
+
|
|
214
|
+
parser.processData(statusData)
|
|
215
|
+
parser.processData(headersData)
|
|
216
|
+
|
|
217
|
+
assert.isTrue(bodyComplete)
|
|
218
|
+
assert.equal(parser.getState(), RequestState.Done)
|
|
219
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
220
|
+
|
|
221
|
+
test('aborts parser correctly', ({ assert }) => {
|
|
222
|
+
parser.abort()
|
|
223
|
+
|
|
224
|
+
assert.isTrue(aborted)
|
|
225
|
+
assert.equal(parser.getState(), RequestState.Done)
|
|
226
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
227
|
+
|
|
228
|
+
test('ignores data after abort', ({ assert }) => {
|
|
229
|
+
parser.abort()
|
|
230
|
+
|
|
231
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
232
|
+
parser.processData(statusData)
|
|
233
|
+
|
|
234
|
+
assert.isFalse(statusParsed)
|
|
235
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
236
|
+
|
|
237
|
+
test('resets parser state correctly', ({ assert }) => {
|
|
238
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
239
|
+
parser.processData(statusData)
|
|
240
|
+
|
|
241
|
+
parser.reset()
|
|
242
|
+
|
|
243
|
+
assert.equal(parser.getState(), RequestState.Status)
|
|
244
|
+
assert.isFalse(parser.getResponseInfo().chunked)
|
|
245
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
246
|
+
|
|
247
|
+
test('can process new response after reset', ({ assert }) => {
|
|
248
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
249
|
+
parser.processData(statusData)
|
|
250
|
+
|
|
251
|
+
parser.reset()
|
|
252
|
+
|
|
253
|
+
parser.processData(statusData)
|
|
254
|
+
assert.isTrue(statusParsed)
|
|
255
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
256
|
+
|
|
257
|
+
const parts = [
|
|
258
|
+
Buffer.from([
|
|
259
|
+
72, 84, 84, 80, 47, 49, 46, 48, 32, 50, 48, 48, 32, 79, 75, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121,
|
|
260
|
+
112, 101, 58, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 13, 10, 67, 111,
|
|
261
|
+
110, 116, 101, 110, 116, 45, 76, 101, 110, 103, 116, 104, 58, 32, 49, 49, 52, 13, 10,
|
|
262
|
+
]),
|
|
263
|
+
Buffer.from([
|
|
264
|
+
83, 101, 114, 118, 101, 114, 58, 32, 87, 101, 114, 107, 122, 101, 117, 103, 47, 48, 46, 49, 52, 46, 49, 32, 80,
|
|
265
|
+
121, 116, 104, 111, 110, 47, 50, 46, 55, 46, 49, 52, 13, 10, 68, 97, 116, 101, 58, 32, 84, 104, 117, 44, 32, 50,
|
|
266
|
+
49, 32, 74, 117, 110, 32, 50, 48, 49, 56, 32, 49, 56, 58, 51, 48, 58, 53, 49, 32, 71, 77, 84, 13, 10, 13, 10, 123,
|
|
267
|
+
34, 105, 110, 115, 116, 114, 117, 109, 101, 110, 116, 95, 105, 100, 34, 58, 32, 50, 52, 49, 56, 54, 44, 32, 34,
|
|
268
|
+
117, 115, 101, 114, 95, 105, 100, 34, 58, 32, 53, 57, 44, 32, 34, 112, 114, 111, 100, 117, 99, 116, 95, 105, 100,
|
|
269
|
+
34, 58, 32, 51, 50, 54, 57, 44, 32, 34, 112, 114, 105, 99, 101, 34, 58, 32, 50, 46, 48, 44, 32, 34, 115, 105, 100,
|
|
270
|
+
101, 34, 58, 32, 34, 83, 101, 108, 108, 34, 44, 32, 34, 105, 100, 34, 58, 32, 50, 44, 32, 34, 113, 117, 97, 110,
|
|
271
|
+
116, 105, 116, 121, 34, 58, 32, 48, 125, 10,
|
|
272
|
+
]),
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
test('processes complete response in multiple parts', ({ assert }) => {
|
|
276
|
+
parser.processData(parts[0])
|
|
277
|
+
parser.processData(parts[1])
|
|
278
|
+
|
|
279
|
+
assert.isTrue(statusParsed)
|
|
280
|
+
assert.isTrue(headersParsed)
|
|
281
|
+
assert.isTrue(bodyComplete)
|
|
282
|
+
assert.equal(parser.getState(), RequestState.Done)
|
|
283
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
284
|
+
|
|
285
|
+
test('extracts correct response information', ({ assert }) => {
|
|
286
|
+
parser.processData(parts[0])
|
|
287
|
+
parser.processData(parts[1])
|
|
288
|
+
|
|
289
|
+
const responseInfo = parser.getResponseInfo()
|
|
290
|
+
assert.equal(responseInfo.status, 200)
|
|
291
|
+
assert.equal(responseInfo.contentLength, 114)
|
|
292
|
+
assert.isFalse(responseInfo.chunked)
|
|
293
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
294
|
+
|
|
295
|
+
test('handles parsing errors gracefully', ({ assert }) => {
|
|
296
|
+
const invalidData = Buffer.from('Invalid HTTP Response Data')
|
|
297
|
+
parser.processData(invalidData)
|
|
298
|
+
|
|
299
|
+
// StatusParser doesn't throw errors for invalid data
|
|
300
|
+
assert.isFalse(errorOccurred)
|
|
301
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
302
|
+
|
|
303
|
+
test('continues processing after error', ({ assert }) => {
|
|
304
|
+
const invalidData = Buffer.from('Invalid Data')
|
|
305
|
+
parser.processData(invalidData)
|
|
306
|
+
|
|
307
|
+
const validData = Buffer.from('HTTP/1.1 200 OK\r\n\r\n')
|
|
308
|
+
parser.processData(validData)
|
|
309
|
+
|
|
310
|
+
// StatusParser doesn't throw errors for invalid data
|
|
311
|
+
assert.isFalse(errorOccurred)
|
|
312
|
+
assert.isTrue(statusParsed)
|
|
313
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
314
|
+
|
|
315
|
+
test('tracks state correctly through parsing stages', ({ assert }) => {
|
|
316
|
+
assert.equal(parser.getState(), RequestState.Status)
|
|
317
|
+
|
|
318
|
+
const statusData = Buffer.from('HTTP/1.1 200 OK\r\n')
|
|
319
|
+
parser.processData(statusData)
|
|
320
|
+
assert.equal(parser.getState(), RequestState.Headers)
|
|
321
|
+
|
|
322
|
+
const headersData = Buffer.from('Content-Length: 4\r\n\r\n')
|
|
323
|
+
parser.processData(headersData)
|
|
324
|
+
assert.equal(parser.getState(), RequestState.Body)
|
|
325
|
+
|
|
326
|
+
const bodyData = Buffer.from('test')
|
|
327
|
+
parser.processData(bodyData)
|
|
328
|
+
assert.equal(parser.getState(), RequestState.Done)
|
|
329
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
330
|
+
|
|
331
|
+
test('returns correct response info', ({ assert }) => {
|
|
332
|
+
const responseInfo = parser.getResponseInfo()
|
|
333
|
+
assert.isFalse(responseInfo.chunked)
|
|
334
|
+
assert.isUndefined(responseInfo.status)
|
|
335
|
+
assert.isUndefined(responseInfo.contentLength)
|
|
336
|
+
}).tags(['@http-engine', '@response', '@parser'])
|
|
337
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test } from '@japa/runner'
|
|
2
2
|
import net from 'net'
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
createLogger,
|
|
5
5
|
CoreEngine,
|
|
6
6
|
IHttpRequest,
|
|
7
7
|
HttpEngineOptions,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
ISerializedError,
|
|
10
10
|
} from '../../../../src/index.js'
|
|
11
11
|
|
|
12
|
-
const logger =
|
|
12
|
+
const logger = createLogger()
|
|
13
13
|
|
|
14
14
|
test.group('Aborting the request', (group) => {
|
|
15
15
|
let request: IHttpRequest
|
|
@@ -70,11 +70,10 @@ test.group('Aborting the request', (group) => {
|
|
|
70
70
|
assert.isUndefined(result)
|
|
71
71
|
}).tags(['@http-engine', '@abort'])
|
|
72
72
|
|
|
73
|
-
test('_createResponse()
|
|
73
|
+
test('_createResponse() throws an error', async ({ assert }) => {
|
|
74
74
|
const base = new CoreEngine(request, opts)
|
|
75
75
|
base.abort()
|
|
76
|
-
|
|
77
|
-
assert.isUndefined(result)
|
|
76
|
+
await assert.rejects(() => base._createResponse(), 'Request aborted.')
|
|
78
77
|
}).tags(['@http-engine', '@abort'])
|
|
79
78
|
|
|
80
79
|
test('aborts the request with a signal', async ({ assert }) => {
|