Hi all,
How would one go about unit testing the changing of channel options from a handler? Using an EmbeddedChannel
, it calls fatalError
for getError(:)
and setError(:value:)
. I have a handler that, when added to the pipeline, sets a channel option. It is similar to the sample from swift-nio-ssh:
It does this in the handler because, I assume, it is a child channel pipeline.
I have some other things being handled in this same handler in my code, such has handling user events properly. The call to handlerAdded(context:)
will always make my tests fail. It's short so I can paste it here:
import Foundation
import NIO
import NIOSSH
import Logging
internal class SshSftpSubsystemServerHandler: ChannelDuplexHandler {
typealias InboundIn = SSHChannelData
typealias InboundOut = ByteBuffer
typealias OutboundIn = ByteBuffer
typealias OutboundOut = SSHChannelData
enum HandlerError: Error, Equatable {
case unexpectedChannelData
case unexpectedDataBeforeInitialized
}
let logger: Logger
var isSftpSubsystemInitialized = false
init(logger: Logger) {
self.logger = logger
}
func handlerAdded(context: ChannelHandlerContext) {
context.channel.setOption(ChannelOptions.allowRemoteHalfClosure, value: true).whenFailure { error in
context.fireErrorCaught(error)
}
}
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
switch event {
case let subsystemRequest as SSHChannelRequestEvent.SubsystemRequest where subsystemRequest.subsystem == "sftp":
isSftpSubsystemInitialized = true
context.triggerUserOutboundEvent(ChannelSuccessEvent(), promise: nil)
case ChannelEvent.inputClosed:
self.logger.info("Client \(String(describing: context.channel.remoteAddress)) disconnected.")
context.close(promise: nil)
default:
context.fireUserInboundEventTriggered(event)
}
}
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
guard isSftpSubsystemInitialized else {
context.fireErrorCaught(HandlerError.unexpectedDataBeforeInitialized)
return
}
let channelData = self.unwrapInboundIn(data)
guard case let .byteBuffer(buffer) = channelData.data else {
context.fireErrorCaught(HandlerError.unexpectedChannelData)
return
}
// Pass along to next handler
switch channelData.type {
case .channel:
context.fireChannelRead(self.wrapInboundOut(buffer))
case .stdErr:
// TODO: Log?
break
default:
break
}
}
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
guard isSftpSubsystemInitialized else {
context.fireErrorCaught(HandlerError.unexpectedDataBeforeInitialized)
return
}
let buffer = self.unwrapOutboundIn(data)
let channelData = SSHChannelData(type: .channel, data: .byteBuffer(buffer))
context.write(self.wrapOutboundOut(channelData), promise: promise)
}
}
I suppose I could ignore unit testing the handlerAdded(context:)
by moving it to a separate handler, so I can write tests for the user events in this handler. Additionally, I suppose something as simple as setting the value of an option is not too important to unit test (it's not like the options would do anything if set on an EmbeddedChannel
anyway). But, I did want to ask here if there's an easy way of doing it.
The only way I can think of doing it right now would be using the ServerBootstrap
to create everything as a normal application. But that would need to bind to an address and port, as well as need a client to connect to it to initialize the channel and everything. Doing that in a unit tests seems overkill.