Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add "address()" on mock Socket #549

Merged

Conversation

mikicho
Copy link
Contributor

@mikicho mikicho commented Apr 13, 2024

No description provided.

@mikicho mikicho changed the base branch from main to feat/yet-another-socket-interceptor April 13, 2024 11:12
@kettanaito
Copy link
Member

Sharing our conversation from Discord:

WDYT about something like realSocket property in the MockSocket class? so in the address function we can do something like: if (mocked) {..} else this.realSocket.address()

That's tempting but I would recommend against it. realSocket introduces a state and I'd rather not have any state (or as little as possible state).

Instead, we know the mocked and passthrough contexts because we have methods respondWith() and passthrough(). Those methods can set the address function on the socket instance based on the context:

passthrough() {
  const realSocket = createConnection()
  // Fro actual connections, infer the address function.
  this.address = realSocket.address
}

respondWith(response) {
  this.address = () => addressFromResponse(response)
}

}
}

return super.address()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have incorrect (empty?) value for bypass scenarios because MockHttpSocket has no connection information.

I proposed to infer this from the original socket but now I wonder, socket.address() is generally available throughout the socket's life-cycle, yes? Like, you can grab the address at any point in time after constructing the socket. Hmm.

});


it.only('returns real socket address', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, accidental it.only committed! Sorry, don't have an eslint for this.

@mikicho
Copy link
Contributor Author

mikicho commented Apr 14, 2024

@kettanaito I fixed this as suggested. I still think we need to separate the socket-related logic from the HTTP-specific one if we want to support more protocols, but that can wait for another PR.

I have a couple of questions before it's ready for merge from my side:

  1. Where should the tests I added be?
  2. What do you think about current fake values? Do you have a better idea?

@kettanaito kettanaito changed the title Michael/fake socket address fix: add "address()" on mock Socket Apr 15, 2024
@@ -124,6 +124,7 @@ export class MockHttpSocket extends MockSocket {
*/
public passthrough(): void {
const socket = this.createConnection()
this.address = () => socket.address()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, this would be safer:

Suggested change
this.address = () => socket.address()
this.address = socket.address.bind(socket)

In case this in the address() points to the socket instance, which I think it does. Let's preserve the context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both ways preserve the context, but I agree that your solution is more explicit.

@@ -205,6 +206,13 @@ export class MockHttpSocket extends MockSocket {
return
}

// Return fake address information for the socket.
this.address = () => ({
address: '0.0.0.0',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about setting 127.0.0.1 as the fake address? Seeing localhost can hint people that this address is, indeed, mocked.

// Return fake address information for the socket.
this.address = () => ({
address: '0.0.0.0',
family: 'IPv4',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there's any info in connectionOptions that we can use to set the family? 🤔

Yeah, there's .family and .host. Do you think we should respect those?

this.address = () => {
  return {
    address: this.connectionOptions?.host || '127.0.0.1',
    family: this.connectionOptions?.family || 'IPv6',
    port: this.connectionOptions?.port || 0
  }
}

We can also add automated tests for the cases when explicit connection options are set. Granted, this is likely better tested in the unit tests on MockHttpSocket. You can leave the existing http.* tests as-is, those are useful too!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the address info resolved in the socket. I didn't verfiy this, but the connectionOptions includes only the port.
Also, please note the host !== address.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Let's infer the port only for now then?

const request = http.get('http://example.com')
request.once('socket', socket => {
socket.once('connect', () => {
connectPromise.resolve(socket.address())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to also have a test for socket.address() before the connect is emitted. I believe it should still be set?

Copy link
Contributor Author

@mikicho mikicho Apr 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the lookup happens after the socket event is emitted.

request.once('socket', socket => {
  console.log(socket.address()); // prints empty object {}
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is correct. ClientRequest emits socket as soon as the socket is assigned, then the socket goes through its life-cycle, emitting lookup and connect.

@mikicho mikicho marked this pull request as ready for review April 15, 2024 18:14
@@ -298,7 +306,7 @@ export class MockHttpSocket extends MockSocket {
}

private mockConnect(): void {
this.emit('lookup', null, '::1', 6, this.connectionOptions.host)
this.emit('lookup', null, '127.0.0.1', 4, this.connectionOptions.host)
Copy link
Member

@kettanaito kettanaito Apr 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think setting this.address should be done in mockConnect() for mocked responses. This will couple the .address() function withe actual address we emit in the lookup even so those two stay consistent (right now, they are not).

const address = {
  host: '...',
  family: '...',
  port: '...'
}
this.address = () => address

this.emit('lookup', null, address.host, address.family, this.connectionOptions.host)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonderful! I'm ashamed that I didn't think about it myself.😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You think of a ton of things that never crossed my mind 🙌 The power of collaboration.

Copy link
Member

@kettanaito kettanaito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! I've pushed a minor polishing to the tests, this should be ready for merge after CI passes.

@kettanaito kettanaito merged commit 66c6046 into feat/yet-another-socket-interceptor Apr 16, 2024
2 checks passed
@kettanaito kettanaito deleted the Michael/fake-socket-address branch April 16, 2024 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants