/* tslint:disable:no-object-literal-type-assertion */ import * as tls from 'tls' import { ArticleResponse, ArticlesResponse, CapabilitiesResponse, CapabilityLabel, DateResponse, DistributionPatternsResponse, GroupResponse, GroupsResponse, HelpResponse, ListHeadersResponse, NewnewsResponse, NntpErrorResponse, NntpResponse, OverviewFormatResponse } from '../src' import * as handlers from '../src/handlers' import { c, client, integrationSetup, s, server } from './IntegrationCommon' integrationSetup() describe('3.2.1.1. Examples', () => { test('Example of an unknown command:', async () => { c('MAIL') s('500 Unknown command') let caught = false return client .command('MAIL') .catch(response => { caught = true expect(response).toEqual({ code: 500, comment: 'Unknown command', description: 'unknown command' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test('Example of an unsupported command:', async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('NEWNEWS') s('LIST ACTIVE NEWSGROUPS') s('.') const response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { [CapabilityLabel.VERSION]: ['2'], [CapabilityLabel.READER]: [], [CapabilityLabel.NEWNEWS]: [], [CapabilityLabel.LIST]: ['ACTIVE', 'NEWSGROUPS'] } } as CapabilitiesResponse) c('OVER') s('500 Unknown command') let caught = false return client .over() .catch(response => { caught = true expect(response).toEqual({ code: 500, comment: 'Unknown command', description: 'unknown command' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test('Example of an unsupported variant:', async () => { c('MODE POSTER') s('501 Unknown MODE option') let caught = false return client .command('MODE', 'POSTER') .catch(response => { caught = true expect(response).toEqual({ code: 501, comment: 'Unknown MODE option', description: 'syntax error in command' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test('Example of a syntax error:', async () => { c('ARTICLE a.message.id@no.angle.brackets') s('501 Syntax error') let caught = false return client .article('a.message.id@no.angle.brackets') .catch(response => { caught = true expect(response).toEqual({ code: 501, comment: 'Syntax error', description: 'syntax error in command' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test('Example of an overlong command line:', async () => { c('HEAD 53 54 55') s('501 Too many arguments') let caught = false return client .command('HEAD', '53', '54', '55') .catch(response => { caught = true expect(response).toEqual({ code: 501, comment: 'Too many arguments', description: 'syntax error in command' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test('Example of a bad wildmat:', async () => { c('LIST ACTIVE u[ks].*') s('501 Syntax error') let caught = false return client .listActive('u[ks].*') .catch(response => { caught = true expect(response).toEqual({ code: 501, comment: 'Syntax error', description: 'syntax error in command' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test( 'Example of a base64-encoding error (the second argument is meant to' + 'be base64 encoded):', async () => { c('XENCRYPT RSA abcd=efg') s('504 Base64 encoding error') let caught = false return client .command('XENCRYPT', 'RSA', 'abcd=efg') .catch(response => { caught = true expect(response).toEqual({ code: 504, comment: 'Base64 encoding error', description: 'error in base64-encoding [RFC4648] of an argument' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) } ) test( 'Example of an attempt to access a facility not available to this' + 'connection:', async () => { c('MODE READER') s('200 Reader mode, posting permitted') const response = await client.modeReader() expect(response).toEqual({ code: 200, comment: 'Reader mode, posting permitted', description: 'Posting allowed' } as NntpResponse) c('IHAVE ') s('500 Permission denied') let caught = false return client .ihave('') .catch(response => { caught = true expect(response).toEqual({ code: 500, comment: 'Permission denied', description: 'unknown command' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) } ) test('Example of an attempt to access a facility requiring authentication:', async () => { c('GROUP secret.group') s('480 Permission denied') let caught = false return client .group('secret.group') .catch(response => { caught = true expect(response).toEqual({ code: 480, comment: 'Permission denied', description: 'command unavailable until the client has authenticated itself' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test('Example of a successful attempt following such authentication:', async () => { c('XSECRET fred flintstone') s('290 Password for fred accepted') // @ts-expect-error handlers.addHandler('XSECRET', 290, handlers.build('Authentication success')) let response = await client.command('XSECRET', 'fred', 'flintstone') expect(response).toEqual({ code: 290, comment: 'Password for fred accepted', description: 'Authentication success' } as NntpResponse) c('GROUP secret.group') s('211 5 1 20 secret.group selected') response = await client.group('secret.group') expect(response).toEqual({ code: 211, comment: 'selected', description: 'Group successfully selected', group: { name: 'secret.group', number: 5, low: 1, high: 20 } } as GroupResponse) }) test.skip('Example of an attempt to access a facility requiring privacy:', async () => { c('GROUP secret.group') s('483 Secure connection required') c('XENCRYPT') // [Client and server negotiate encryption on the link] s('283 Encrypted link established', server.upgradeTls) // @ts-expect-error handlers.addHandler('XENCRYPT', 283, handlers.build('Encryption success')) let response = await client.command('XENCRYPT') expect(response).toEqual({ code: 283, comment: 'Encrypted link established', description: 'Encryption success' } as NntpResponse) const socket = await client._connection.upgradeTls() expect(socket).toEqual(expect.any(tls.TLSSocket)) c('GROUP secret.group') s('211 5 1 20 secret.group selected') response = await client.group('secret.group') expect(response).toEqual({ code: 211, comment: 'selected', description: 'Group successfully selected', group: { name: 'secret.group', number: 5, low: 1, high: 20 } } as GroupResponse) }) test('Example of a need to change mode before a facility is used:', async () => { c('GROUP binary.group') s('401 XHOST Not on this virtual host') let caught = false try { await client.group('binary.group') } catch (response) { // @ts-expect-error expect(response).toEqual({ code: 401, capabilityLabel: 'XHOST', comment: 'Not on this virtual host', description: 'the server is in the wrong mode; the indicated capability should be used to change the mode.' } as NntpErrorResponse) caught = true } expect(caught).toBe(true) c('XHOST binary.news.example.org') s('290 binary.news.example.org virtual host selected') // @ts-expect-error handlers.addHandler('XHOST', 290, { multiLine: false, numberOfArgs: 1, callback: args => ({ description: 'Host successfully selected', host: args[0] }) }) let response = await client.command('XHOST', 'binary.news.example.org') expect(response).toEqual({ code: 290, comment: 'virtual host selected', description: 'Host successfully selected', host: 'binary.news.example.org' }) c('GROUP binary.group') s('211 5 1 77 binary.group selected') response = await client.group('binary.group') expect(response).toEqual({ code: 211, comment: 'selected', description: 'Group successfully selected', group: { name: 'binary.group', number: 5, low: 1, high: 77 } } as GroupResponse) }) test('Example of a temporary failure:', async () => { c('GROUP archive.local') s('403 Archive server temporarily offline') let caught = false return client .group('archive.local') .catch(response => { expect(response).toEqual({ code: 403, comment: 'Archive server temporarily offline', description: 'internal fault or problem preventing action being taken' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test('Example of the server needing to close down immediately:', async () => { c('ARTICLE 123') s('400 Power supply failed, running on UPS') // [Server closes connection.] let caught = false return client .article(123) .catch(response => { caught = true expect(response).toEqual({ code: 400, comment: 'Power supply failed, running on UPS', description: 'service not available or no longer available' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) }) describe('3.5.1. Examples', () => { test('Example of correct use of pipelining:', async () => { c('GROUP misc.test') c('STAT') c('NEXT') s('211 1234 3000234 3002322 misc.test') s('223 3000234 <45223423@example.com> retrieved') s('223 3000237 <668929@example.org> retrieved') // Setup pipelined response (awkward with MockServer) server.resetHandlers() server.addSimpleHandler((command, args) => { expect(command).toEqual('GROUP') expect(args).toEqual(['misc.test\r\nSTAT\r\nNEXT']) return ( '211 1234 3000234 3002322 misc.test\r\n' + '223 3000234 <45223423@example.com> retrieved\r\n' + '223 3000237 <668929@example.org> retrieved\r\n' ) }) const responses = await Promise.all([client.group('misc.test'), client.stat(), client.next()]) expect(responses).toEqual([ { code: 211, comment: '', description: 'Group successfully selected', group: { name: 'misc.test', high: 3002322, low: 3000234, number: 1234 } } as GroupResponse, { article: { articleNumber: 3000234, messageId: '<45223423@example.com>' }, code: 223, comment: 'retrieved', description: 'Article exists' } as ArticleResponse, { article: { articleNumber: 3000237, messageId: '<668929@example.org>' }, code: 223, comment: 'retrieved', description: 'Article found' } as ArticleResponse ]) }) test.skip( 'Example of incorrect use of pipelining (the MODE READER command may' + 'not be pipelined):', async () => { c('MODE READER') c('DATE') c('NEXT') s('200 Server ready, posting allowed') s('223 3000237 <668929@example.org> retrieved') } ) }) describe('4.4. Examples', () => { // In these examples, $ and @ are used to represent the two octets %xC2 // and %xA3, respectively; $@ is thus the UTF-8 encoding for the pound // sterling symbol, shown as # in the descriptions. // Wildmat Description of strings that match // abc The one string "abc" // abc,def The two strings "abc" and "def" // $@ The one character string "#" // a* Any string that begins with "a" // a*b Any string that begins with "a" and ends with "b" // a*,*b Any string that begins with "a" or ends with "b" // a*,!*b Any string that begins with "a" and does not end with // "b" // a*,!*b,c* Any string that begins with "a" and does not end with // "b", and any string that begins with "c" no matter // what it ends with // a*,c*,!*b Any string that begins with "a" or "c" and does not // end with "b" // ?a* Any string with "a" as its second character // ??a* Any string with "a" as its third character // *a? Any string with "a" as its penultimate character // *a?? Any string with "a" as its antepenultimate character }) describe('5.1.3. Examples', () => { test(`Example of a normal connection from an authorized client that then terminates the session (see Section 5.4):`, async () => { // [Initial connection set-up completed.] s('200 NNTP Service Ready, posting permitted') c('QUIT') s('205 NNTP Service exits normally') const response = await client.quit() expect(response).toEqual({ code: 205, comment: 'NNTP Service exits normally', description: 'Connection closing' } as NntpResponse) // [Server closes connection.] }) test(`Example of a normal connection from an authorized client that is not permitted to post, which also immediately terminates the session:`, async () => { // [Initial connection set-up completed.] s('201 NNTP Service Ready, posting prohibited') c('QUIT') s('205 NNTP Service exits normally') const response = await client.quit() expect(response).toEqual({ code: 205, comment: 'NNTP Service exits normally', description: 'Connection closing' } as NntpResponse) // [Server closes connection.] }) test.skip('Example of a normal connection from an unauthorized client:', async () => { // [Initial connection set-up completed.] s('502 NNTP Service permanently unavailable') // [Server closes connection.] }) test.skip(`Example of a connection from a client if the server is unable to provide service:`, async () => { // [Initial connection set-up completed.] s('400 NNTP Service temporarily unavailable') // [Server closes connection.] }) }) describe('5.2.3. Examples', () => { test('Example of a minimal response (a read-only server):', async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('LIST ACTIVE NEWSGROUPS') s('.') const response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { [CapabilityLabel.VERSION]: ['2'], [CapabilityLabel.READER]: [], [CapabilityLabel.LIST]: ['ACTIVE', 'NEWSGROUPS'] } } as CapabilitiesResponse) }) test(`Example of a response from a server that has a range of facilities and that also describes itself:`, async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('IHAVE') s('POST') s('NEWNEWS') s('LIST ACTIVE NEWSGROUPS ACTIVE.TIMES OVERVIEW.FMT') s('IMPLEMENTATION INN 4.2 2004-12-25') s('OVER MSGID') s('STREAMING') s('XSECRET') s('.') const response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], IHAVE: [], POST: [], NEWNEWS: [], LIST: ['ACTIVE', 'NEWSGROUPS', 'ACTIVE.TIMES', 'OVERVIEW.FMT'], IMPLEMENTATION: ['INN', '4.2', '2004-12-25'], OVER: ['MSGID'], STREAMING: [], XSECRET: [] } }) }) test('Example of a server that supports more than one version of NNTP:', async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2 3') s('READER') s('LIST ACTIVE NEWSGROUPS') s('.') const response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2', '3'], READER: [], LIST: ['ACTIVE', 'NEWSGROUPS'] } } as CapabilitiesResponse) }) test(`Example of a client attempting to use a feature of the CAPABILITIES command that the server does not support:`, async () => { c('CAPABILITIES AUTOUPDATE') s('101 Capability list:') s('VERSION 2') s('READER') s('IHAVE') s('LIST ACTIVE NEWSGROUPS OVERVIEW.FMT HEADERS') s('OVER MSGID') s('HDR') s('NEWNEWS') s('.') const response = await client.capabilities('AUTOUPDATE') expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], IHAVE: [], LIST: ['ACTIVE', 'NEWSGROUPS', 'OVERVIEW.FMT', 'HEADERS'], OVER: ['MSGID'], HDR: [], NEWNEWS: [] } } as CapabilitiesResponse) }) }) describe('5.3.3. Examples', () => { test(`Example of use of the MODE READER command on a transit-only server (which therefore does not providing reading facilities):`, async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('IHAVE') s('.') const response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], IHAVE: [] } } as CapabilitiesResponse) c('MODE READER') s('502 Transit service only') // [Server closes connection.] let caught = false return client .modeReader() .catch(response => { expect(response).toEqual({ code: 502, comment: 'Transit service only', description: 'Reading service permanently unavailable' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of use of the MODE READER command on a server that provides reading facilities:`, async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('LIST ACTIVE NEWSGROUPS') s('.') let response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], LIST: ['ACTIVE', 'NEWSGROUPS'] } } as CapabilitiesResponse) c('MODE READER') s('200 Reader mode, posting permitted') response = await client.modeReader() expect(response).toEqual({ code: 200, comment: 'Reader mode, posting permitted', description: 'Posting allowed' } as NntpResponse) c('IHAVE ') s('500 Permission denied') let caught = false try { await client.ihave('') } catch (response) { caught = true expect(response).toEqual({ code: 500, comment: 'Permission denied', description: 'unknown command' } as NntpErrorResponse) } expect(caught).toBe(true) c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) }) test('Example of use of the MODE READER command on a mode-switching server:', async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('IHAVE') s('MODE-READER') s('.') let response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { [CapabilityLabel.VERSION]: ['2'], [CapabilityLabel.IHAVE]: [], [CapabilityLabel.MODE_READER]: [] } } as CapabilitiesResponse) c('MODE READER') s('200 Reader mode, posting permitted') response = await client.modeReader() expect(response).toEqual({ code: 200, comment: 'Reader mode, posting permitted', description: 'Posting allowed' } as NntpResponse) c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('NEWNEWS') s('LIST ACTIVE NEWSGROUPS') s('STARTTLS') s('.') response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], NEWNEWS: [], LIST: ['ACTIVE', 'NEWSGROUPS'], STARTTLS: [] } } as CapabilitiesResponse) }) test(`Example of use of the MODE READER command where the client is not permitted to post:`, async () => { c('MODE READER') s('201 NNTP Service Ready, posting prohibited') const response = await client.modeReader() expect(response).toEqual({ code: 201, comment: 'NNTP Service Ready, posting prohibited', description: 'Posting prohibited' } as NntpResponse) }) }) describe.skip('5.4.3. Examples', () => { test('examples', async () => { c('QUIT') s('205 closing connection') // [Server closes connection.] const response = await client.quit() expect(response).toEqual({ code: 205, comment: 'closing connection', description: 'Connection closing' } as NntpResponse) }) }) describe('6.1.1.3. Examples', () => { test('Example for a group known to the server:', async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { name: 'misc.test', number: 1234, low: 3000234, high: 3002322 } } as GroupResponse) }) test('Example for a group unknown to the server:', async () => { c('GROUP example.is.sob.bradner.or.barber') s('411 example.is.sob.bradner.or.barber is unknown') let caught = false return client .group('example.is.sob.bradner.or.barber') .catch(response => { expect(response).toEqual({ code: 411, comment: 'example.is.sob.bradner.or.barber is unknown', description: 'No such newsgroup' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test('Example of an empty group using the preferred response:', async () => { c('GROUP example.currently.empty.newsgroup') s('211 0 4000 3999 example.currently.empty.newsgroup') const response = await client.group('example.currently.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 4000, high: 3999, name: 'example.currently.empty.newsgroup' } } as GroupResponse) }) test('Example of an empty group using an alternative response:', async () => { c('GROUP example.currently.empty.newsgroup') s('211 0 0 0 example.currently.empty.newsgroup') const response = await client.group('example.currently.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.currently.empty.newsgroup' } } as GroupResponse) }) test('Example of an empty group using a different alternative response:', async () => { c('GROUP example.currently.empty.newsgroup') s('211 0 4000 4321 example.currently.empty.newsgroup') const response = await client.group('example.currently.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 4000, high: 4321, name: 'example.currently.empty.newsgroup' } } as GroupResponse) }) test('Example reselecting the currently selected newsgroup:', async () => { c('GROUP misc.test') s('211 1234 234 567 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 234, high: 567, name: 'misc.test' } } as GroupResponse) c('STAT 444') s('223 444 <123456@example.net> retrieved') response = await client.stat(444) expect(response).toEqual({ code: 223, comment: 'retrieved', description: 'Article exists', article: { articleNumber: 444, messageId: '<123456@example.net>' } } as ArticleResponse) c('GROUP misc.test') s('211 1234 234 567 misc.test') response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 234, high: 567, name: 'misc.test' } } as GroupResponse) c('STAT') s('223 234 retrieved') response = await client.stat() expect(response).toEqual({ code: 223, comment: 'retrieved', description: 'Article exists', article: { articleNumber: 234, messageId: '' } } as ArticleResponse) }) }) describe('6.1.2.3. Examples', () => { test('Example of LISTGROUP being used to select a group:', async () => { c('LISTGROUP misc.test') s('211 2000 3000234 3002322 misc.test list follows') s('3000234') s('3000237') s('3000238') s('3000239') s('3002322') s('.') const response = await client.listGroup('misc.test') expect(response).toEqual({ code: 211, comment: 'list follows', description: 'Article numbers follow (multi-line)', group: { number: 2000, low: 3000234, high: 3002322, name: 'misc.test', articleNumbers: [3000234, 3000237, 3000238, 3000239, 3002322] } } as GroupResponse) }) test('Example of LISTGROUP on an empty group:', async () => { c('LISTGROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup list follows') s('.') const response = await client.listGroup('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: 'list follows', description: 'Article numbers follow (multi-line)', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup', articleNumbers: [] } } as GroupResponse) }) test('Example of LISTGROUP on a valid, currently selected newsgroup:', async () => { c('GROUP misc.test') s('211 2000 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 2000, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('LISTGROUP') s('211 2000 3000234 3002322 misc.test list follows') s('3000234') s('3000237') s('3000238') s('3000239') s('3002322') s('.') response = await client.listGroup() expect(response).toEqual({ code: 211, comment: 'list follows', description: 'Article numbers follow (multi-line)', group: { number: 2000, low: 3000234, high: 3002322, name: 'misc.test', articleNumbers: [3000234, 3000237, 3000238, 3000239, 3002322] } } as GroupResponse) }) test('Example of LISTGROUP with a range:', async () => { c('LISTGROUP misc.test 3000238-3000248') s('211 2000 3000234 3002322 misc.test list follows') s('3000238') s('3000239') s('.') const response = await client.listGroup('misc.test', { start: 3000238, end: 3000248 }) expect(response).toEqual({ code: 211, comment: 'list follows', description: 'Article numbers follow (multi-line)', group: { number: 2000, low: 3000234, high: 3002322, name: 'misc.test', articleNumbers: [3000238, 3000239] } } as GroupResponse) }) test('Example of LISTGROUP with an empty range:', async () => { c('LISTGROUP misc.test 12345678-') s('211 2000 3000234 3002322 misc.test list follows') s('.') const response = await client.listGroup('misc.test', { start: 12345678 }) expect(response).toEqual({ code: 211, comment: 'list follows', description: 'Article numbers follow (multi-line)', group: { number: 2000, low: 3000234, high: 3002322, name: 'misc.test', articleNumbers: [] } } as GroupResponse) }) test('Example of LISTGROUP with an invalid range:', async () => { c('LISTGROUP misc.test 9999-111') s('211 2000 3000234 3002322 misc.test list follows') s('.') const response = await client.listGroup('misc.test', { start: 9999, end: 111 }) expect(response).toEqual({ code: 211, comment: 'list follows', description: 'Article numbers follow (multi-line)', group: { number: 2000, low: 3000234, high: 3002322, name: 'misc.test', articleNumbers: [] } } as GroupResponse) }) }) describe('6.1.3.3. Examples', () => { test('Example of a successful article retrieval using LAST:', async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('NEXT') s('223 3000237 <668929@example.org> retrieved') response = await client.next() expect(response).toEqual({ code: 223, comment: 'retrieved', description: 'Article found', article: { articleNumber: 3000237, messageId: '<668929@example.org>' } } as ArticleResponse) c('LAST') s('223 3000234 <45223423@example.com> retrieved') response = await client.last() expect(response).toEqual({ code: 223, comment: 'retrieved', description: 'Article found', article: { articleNumber: 3000234, messageId: '<45223423@example.com>' } } as ArticleResponse) }) test(`Example of an attempt to retrieve an article without having selected a group (via the GROUP command) first:`, async () => { // [Assumes currently selected newsgroup is invalid.] c('LAST') s('412 no newsgroup selected') let caught = false return client .last() .catch(response => { expect(response).toEqual({ code: 412, comment: 'no newsgroup selected', description: 'No newsgroup selected' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an attempt to retrieve an article using the LAST command when the current article number is that of the first article in the group:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('LAST') s('422 No previous article to retrieve') let caught = false return client .last() .catch(response => { expect(response).toEqual({ code: 422, comment: 'No previous article to retrieve', description: 'No previous article in this group' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an attempt to retrieve an article using the LAST command when the currently selected newsgroup is empty:`, async () => { c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') const response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } } as GroupResponse) c('LAST') s('420 No current article selected') let caught = false return client .last() .catch(response => { expect(response).toEqual({ code: 420, comment: 'No current article selected', description: 'Current article number is invalid' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) }) describe('6.1.4.3. Examples', () => { test('Example of a successful article retrieval using NEXT:', async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('NEXT') s('223 3000237 <668929@example.org> retrieved') response = await client.next() expect(response).toEqual({ code: 223, comment: 'retrieved', description: 'Article found', article: { articleNumber: 3000237, messageId: '<668929@example.org>' } } as ArticleResponse) }) test(`Example of an attempt to retrieve an article without having selected a group (via the GROUP command) first:`, async () => { // [Assumes currently selected newsgroup is invalid.] c('NEXT') s('412 no newsgroup selected') let caught = false return client .next() .catch(response => { expect(response).toEqual({ code: 412, comment: 'no newsgroup selected', description: 'No newsgroup selected' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an attempt to retrieve an article using the NEXT command when the current article number is that of the last article in the group:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('STAT 3002322') s('223 3002322 <411@example.net> retrieved') response = await client.stat(3002322) expect(response).toEqual({ code: 223, comment: 'retrieved', description: 'Article exists', article: { articleNumber: 3002322, messageId: '<411@example.net>' } } as ArticleResponse) c('NEXT') s('421 No next article to retrieve') let caught = false return client .next() .catch(response => { expect(response).toEqual({ code: 421, comment: 'No next article to retrieve', description: 'No next article in this group' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an attempt to retrieve an article using the NEXT command when the currently selected newsgroup is empty:`, async () => { c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') const response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } } as GroupResponse) c('NEXT') s('420 No current article selected') let caught = false return client .next() .catch(response => { caught = true expect(response).toEqual({ code: 420, comment: 'No current article selected', description: 'Current article number is invalid' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) }) describe('6.2.1.3. Examples', () => { test(`Example of a successful retrieval of an article (explicitly not using an article number):`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('ARTICLE') s('220 3000234 <45223423@example.com>') s('Path: pathost!demo!whitehouse!not-for-mail') s('From: "Demo User" ') s('Newsgroups: misc.test') s('Subject: I am just a test article') s('Date: 6 Oct 1998 04:38:40 -0500') s('Organization: An Example Net, Uncertain, Texas') s('Message-ID: <45223423@example.com>') s('') s('This is just a test article.') s('.') response = await client.article() expect(response).toEqual({ code: 220, comment: '', description: 'Article follows (multi-line)', article: { articleNumber: 3000234, messageId: '<45223423@example.com>', headers: { PATH: 'pathost!demo!whitehouse!not-for-mail', FROM: '"Demo User" ', NEWSGROUPS: 'misc.test', SUBJECT: 'I am just a test article', DATE: '6 Oct 1998 04:38:40 -0500', ORGANIZATION: 'An Example Net, Uncertain, Texas', 'MESSAGE-ID': '<45223423@example.com>' }, body: ['This is just a test article.'] } } as ArticleResponse) }) test('Example of a successful retrieval of an article by message-id:', async () => { c('ARTICLE <45223423@example.com>') s('220 0 <45223423@example.com>') s('Path: pathost!demo!whitehouse!not-for-mail') s('From: "Demo User" ') s('Newsgroups: misc.test') s('Subject: I am just a test article') s('Date: 6 Oct 1998 04:38:40 -0500') s('Organization: An Example Net, Uncertain, Texas') s('Message-ID: <45223423@example.com>') s('') s('This is just a test article.') s('.') const response = await client.article('<45223423@example.com>') expect(response).toEqual({ code: 220, comment: '', description: 'Article follows (multi-line)', article: { articleNumber: 0, messageId: '<45223423@example.com>', headers: { PATH: 'pathost!demo!whitehouse!not-for-mail', FROM: '"Demo User" ', NEWSGROUPS: 'misc.test', SUBJECT: 'I am just a test article', DATE: '6 Oct 1998 04:38:40 -0500', ORGANIZATION: 'An Example Net, Uncertain, Texas', 'MESSAGE-ID': '<45223423@example.com>' }, body: ['This is just a test article.'] } } as ArticleResponse) }) test('Example of an unsuccessful retrieval of an article by message-id:', async () => { c('ARTICLE ') s('430 No Such Article Found') let caught = false return client .article('') .catch(response => { expect(response).toEqual({ code: 430, comment: 'No Such Article Found', description: 'No article with that message-id' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test('Example of an unsuccessful retrieval of an article by number:', async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 news.groups') // TODO: how can server respond with a different group?? Is RFC wrong? const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'news.groups' } } as GroupResponse) c('ARTICLE 300256') s('423 No article with that number') let caught = false return client .article(300256) .catch(response => { expect(response).toEqual({ code: 423, comment: 'No article with that number', description: 'No article with that number' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an unsuccessful retrieval of an article by number because no newsgroup was selected first:`, async () => { // [Assumes currently selected newsgroup is invalid.] c('ARTICLE 300256') s('412 No newsgroup selected') let caught = false return client .article(300256) .catch(response => { expect(response).toEqual({ code: 412, comment: 'No newsgroup selected', description: 'No newsgroup selected' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an attempt to retrieve an article when the currently selected newsgroup is empty:`, async () => { c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') const response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } } as GroupResponse) c('ARTICLE') s('420 No current article selected') let caught = false return client .article() .catch(response => { expect(response).toEqual({ code: 420, comment: 'No current article selected', description: 'Current article number is invalid' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) }) describe('6.2.2.3. Examples', () => { test(`Example of a successful retrieval of the headers of an article (explicitly not using an article number):`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('HEAD') s('221 3000234 <45223423@example.com>') s('Path: pathost!demo!whitehouse!not-for-mail') s('From: "Demo User" ') s('Newsgroups: misc.test') s('Subject: I am just a test article') s('Date: 6 Oct 1998 04:38:40 -0500') s('Organization: An Example Net, Uncertain, Texas') s('Message-ID: <45223423@example.com>') s('.') response = await client.head() expect(response).toEqual({ code: 221, comment: '', description: 'Headers follow (multi-line)', article: { articleNumber: 3000234, messageId: '<45223423@example.com>', headers: { PATH: 'pathost!demo!whitehouse!not-for-mail', FROM: '"Demo User" ', NEWSGROUPS: 'misc.test', SUBJECT: 'I am just a test article', DATE: '6 Oct 1998 04:38:40 -0500', ORGANIZATION: 'An Example Net, Uncertain, Texas', 'MESSAGE-ID': '<45223423@example.com>' } } } as ArticleResponse) }) test(`Example of a successful retrieval of the headers of an article by message-id:`, async () => { c('HEAD <45223423@example.com>') s('221 0 <45223423@example.com>') s('Path: pathost!demo!whitehouse!not-for-mail') s('From: "Demo User" ') s('Newsgroups: misc.test') s('Subject: I am just a test article') s('Date: 6 Oct 1998 04:38:40 -0500') s('Organization: An Example Net, Uncertain, Texas') s('Message-ID: <45223423@example.com>') s('.') const response = await client.head('<45223423@example.com>') expect(response).toEqual({ code: 221, comment: '', description: 'Headers follow (multi-line)', article: { articleNumber: 0, messageId: '<45223423@example.com>', headers: { PATH: 'pathost!demo!whitehouse!not-for-mail', FROM: '"Demo User" ', NEWSGROUPS: 'misc.test', SUBJECT: 'I am just a test article', DATE: '6 Oct 1998 04:38:40 -0500', ORGANIZATION: 'An Example Net, Uncertain, Texas', 'MESSAGE-ID': '<45223423@example.com>' } } } as ArticleResponse) }) test(`Example of an unsuccessful retrieval of the headers of an article by message-id:`, async () => { c('HEAD ') s('430 No Such Article Found') let caught = false return client .head('') .catch(response => { expect(response).toEqual({ code: 430, comment: 'No Such Article Found', description: 'No article with that message-id' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an unsuccessful retrieval of the headers of an article by number:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('HEAD 300256') s('423 No article with that number') let caught = false return client .head(300256) .catch(response => { expect(response).toEqual({ code: 423, comment: 'No article with that number', description: 'No article with that number' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an unsuccessful retrieval of the headers of an article by number because no newsgroup was selected first:`, async () => { // [Assumes currently selected newsgroup is invalid.] c('HEAD 300256') s('412 No newsgroup selected') let caught = false return client .head(300256) .catch(response => { expect(response).toEqual({ code: 412, comment: 'No newsgroup selected', description: 'No newsgroup selected' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an attempt to retrieve the headers of an article when the currently selected newsgroup is empty:`, async () => { c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') const response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } }) c('HEAD') s('420 No current article selected') let caught = false return client .head() .catch(response => { expect(response).toEqual({ code: 420, comment: 'No current article selected', description: 'Current article number is invalid' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) }) describe('6.2.3.3. Examples', () => { test(`Example of a successful retrieval of the body of an article (explicitly not using an article number):`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('BODY') s('222 3000234 <45223423@example.com>') s('This is just a test article.') s('.') response = await client.body() expect(response).toEqual({ code: 222, comment: '', description: 'Body follows (multi-line)', article: { articleNumber: 3000234, messageId: '<45223423@example.com>', body: ['This is just a test article.'] } } as ArticleResponse) }) test(`Example of a successful retrieval of the body of an article by message-id:`, async () => { c('BODY <45223423@example.com>') s('222 0 <45223423@example.com>') s('This is just a test article.') s('.') const response = await client.body('<45223423@example.com>') expect(response).toEqual({ code: 222, comment: '', description: 'Body follows (multi-line)', article: { articleNumber: 0, messageId: '<45223423@example.com>', body: ['This is just a test article.'] } } as ArticleResponse) }) test(`Example of an unsuccessful retrieval of the body of an article by message-id:`, async () => { c('BODY ') s('430 No Such Article Found') let caught = false return client .body('') .catch(response => { expect(response).toEqual({ code: 430, comment: 'No Such Article Found', description: 'No article with that message-id' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an unsuccessful retrieval of the body of an article by number:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('BODY 300256') s('423 No article with that number') let caught = false return client .body(300256) .catch(response => { expect(response).toEqual({ code: 423, comment: 'No article with that number', description: 'No article with that number' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an unsuccessful retrieval of the body of an article by number because no newsgroup was selected first:`, async () => { // [Assumes currently selected newsgroup is invalid.] c('BODY 300256') s('412 No newsgroup selected') let caught = false return client .body(300256) .catch(response => { expect(response).toEqual({ code: 412, comment: 'No newsgroup selected', description: 'No newsgroup selected' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of an attempt to retrieve the body of an article when the currently selected newsgroup is empty:`, async () => { c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') const response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } } as GroupResponse) c('BODY') s('420 No current article selected') let caught = false return client .body() .catch(response => { expect(response).toEqual({ code: 420, comment: 'No current article selected', description: 'Current article number is invalid' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) }) describe('6.2.4.3. Examples', () => { test(`Example of STAT on an existing article (explicitly not using an article number):`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('STAT') s('223 3000234 <45223423@example.com>') response = await client.stat() expect(response).toEqual({ code: 223, comment: '', description: 'Article exists', article: { articleNumber: 3000234, messageId: '<45223423@example.com>' } } as ArticleResponse) }) test('Example of STAT on an existing article by message-id:', async () => { c('STAT <45223423@example.com>') s('223 0 <45223423@example.com>') const response = await client.stat('<45223423@example.com>') expect(response).toEqual({ code: 223, comment: '', description: 'Article exists', article: { articleNumber: 0, messageId: '<45223423@example.com>' } } as ArticleResponse) }) test('Example of STAT on an article not on the server by message-id:', async () => { c('STAT ') s('430 No Such Article Found') let caught = false return client .stat('') .catch(response => { expect(response).toEqual({ code: 430, comment: 'No Such Article Found', description: 'No article with that message-id' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test('Example of STAT on an article not in the server by number:', async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('STAT 300256') s('423 No article with that number') let caught = false return client .stat(300256) .catch(response => { expect(response).toEqual({ code: 423, comment: 'No article with that number', description: 'No article with that number' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of STAT on an article by number when no newsgroup was selected first:`, async () => { // [Assumes currently selected newsgroup is invalid.] c('STAT 300256') s('412 No newsgroup selected') let caught = false return client .stat(300256) .catch(response => { expect(response).toEqual({ code: 412, comment: 'No newsgroup selected', description: 'No newsgroup selected' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of STAT on an article when the currently selected newsgroup is empty:`, async () => { c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') const response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } } as GroupResponse) c('STAT') s('420 No current article selected') let caught = false return client .stat() .catch(response => { expect(response).toEqual({ code: 420, comment: 'No current article selected', description: 'Current article number is invalid' } as NntpErrorResponse) caught = true }) .then(() => expect(caught).toBe(true)) }) test(`Example of STAT by message-id on a server that sometimes reports the actual article number:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('STAT') s('223 3000234 <45223423@example.com>') response = await client.stat() expect(response).toEqual({ code: 223, comment: '', description: 'Article exists', article: { articleNumber: 3000234, messageId: '<45223423@example.com>' } } as ArticleResponse) c('STAT <45223423@example.com>') s('223 0 <45223423@example.com>') response = await client.stat('<45223423@example.com>') expect(response).toEqual({ code: 223, comment: '', description: 'Article exists', article: { articleNumber: 0, messageId: '<45223423@example.com>' } } as ArticleResponse) c('STAT <45223423@example.com>') s('223 3000234 <45223423@example.com>') response = await client.stat('<45223423@example.com>') expect(response).toEqual({ code: 223, comment: '', description: 'Article exists', article: { articleNumber: 3000234, messageId: '<45223423@example.com>' } } as ArticleResponse) c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } } as GroupResponse) c('STAT <45223423@example.com>') s('223 0 <45223423@example.com>') response = await client.stat('<45223423@example.com>') expect(response).toEqual({ code: 223, comment: '', description: 'Article exists', article: { articleNumber: 0, messageId: '<45223423@example.com>' } } as ArticleResponse) c('GROUP alt.crossposts') s('211 9999 111111 222222 alt.crossposts') response = await client.group('alt.crossposts') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 9999, low: 111111, high: 222222, name: 'alt.crossposts' } } as GroupResponse) c('STAT <45223423@example.com>') s('223 123456 <45223423@example.com>') response = await client.stat('<45223423@example.com>') expect(response).toEqual({ code: 223, comment: '', description: 'Article exists', article: { articleNumber: 123456, messageId: '<45223423@example.com>' } } as ArticleResponse) c('STAT') s('223 111111 <23894720@example.com>') response = await client.stat() expect(response).toEqual({ code: 223, comment: '', description: 'Article exists', article: { articleNumber: 111111, messageId: '<23894720@example.com>' } } as ArticleResponse) }) }) describe('6.3.1.3. Examples', () => { test('Example of a successful posting:', async () => { c('POST') s('340 Input article; end with .') let response = await client.post() expect(response).toEqual({ code: 340, comment: 'Input article; end with .', description: 'Send article to be posted', send: expect.any(Function) }) c( 'From: "Demo User" ', 'Newsgroups: misc.test', 'Subject: I am just a test article', 'Organization: An Example Net', '', 'This is just a test article.', '.' ) s('240 Article received OK') response = await response.send({ headers: { From: '"Demo User" ', Newsgroups: 'misc.test', Subject: 'I am just a test article', Organization: 'An Example Net' }, body: ['This is just a test article.'] }) expect(response).toEqual({ code: 240, comment: 'Article received OK', description: 'Article received OK' }) }) test('Example of an unsuccessful posting:', async () => { c('POST') s('340 Input article; end with .') const response = await client.post() expect(response).toEqual({ code: 340, comment: 'Input article; end with .', description: 'Send article to be posted', send: expect.any(Function) }) c( 'From: "Demo User" ', 'Newsgroups: misc.test', 'Subject: I am just a test article', 'Organization: An Example Net', '', 'This is just a test article.', '.' ) s('441 Posting failed') let caught = false return response .send({ headers: { From: '"Demo User" ', Newsgroups: 'misc.test', Subject: 'I am just a test article', Organization: 'An Example Net' }, body: ['This is just a test article.'] }) .catch(response => { caught = true expect(response).toEqual({ code: 441, comment: 'Posting failed', description: 'Posting failed' }) }) .then(() => expect(caught).toBe(true)) }) test('Example of an attempt to post when posting is not allowed:', async () => { // [Initial connection set-up completed.] s('201 NNTP Service Ready, posting prohibited') c('POST') s('440 Posting not permitted') let caught = false return client .post() .catch(response => { caught = true expect(response).toEqual({ code: 440, comment: 'Posting not permitted', description: 'Posting not permitted' }) }) .then(() => expect(caught).toBe(true)) }) }) describe('6.3.2.3. Examples', () => { test('Example of successfully sending an article to another site:', async () => { c('IHAVE ') s('335 Send it; end with .') let response = await client.ihave('') expect(response).toEqual({ code: 335, comment: 'Send it; end with .', description: 'Send article to be transferred', send: expect.any(Function) }) c( 'Path: pathost!demo!somewhere!not-for-mail', 'From: "Demo User" ', 'Newsgroups: misc.test', 'Subject: I am just a test article', 'Date: 6 Oct 1998 04:38:40 -0500', 'Organization: An Example Com, San Jose, CA', 'Message-ID: ', '', 'This is just a test article.', '.' ) s('235 Article transferred OK') response = await response.send({ headers: { Path: 'pathost!demo!somewhere!not-for-mail', From: '"Demo User" ', Newsgroups: 'misc.test', Subject: 'I am just a test article', Date: '6 Oct 1998 04:38:40 -0500', Organization: 'An Example Com, San Jose, CA', 'Message-ID': '' }, body: ['This is just a test article.'] }) expect(response).toEqual({ code: 235, comment: 'Article transferred OK', description: 'Article transferred OK' }) }) test(`Example of sending an article to another site that rejects it. Note that the message-id in the IHAVE command is not the same as the one in the article headers; while this is bad practice and SHOULD NOT be done, it is not forbidden.`, async () => { c('IHAVE ') s('335 Send it; end with .') const response = await client.ihave('') expect(response).toEqual({ code: 335, comment: 'Send it; end with .', description: 'Send article to be transferred', send: expect.any(Function) }) c( 'Path: pathost!demo!somewhere!not-for-mail', 'From: "Demo User" ', 'Newsgroups: misc.test', 'Subject: I am just a test article', 'Date: 6 Oct 1998 04:38:40 -0500', 'Organization: An Example Com, San Jose, CA', 'Message-ID: ', '', 'This is just a test article.', '.' ) s("437 Article rejected; don't send again") let caught = false return response .send({ headers: { Path: 'pathost!demo!somewhere!not-for-mail', From: '"Demo User" ', Newsgroups: 'misc.test', Subject: 'I am just a test article', Date: '6 Oct 1998 04:38:40 -0500', Organization: 'An Example Com, San Jose, CA', 'Message-ID': '' }, body: ['This is just a test article.'] }) .catch(response => { caught = true expect(response).toEqual({ code: 437, comment: "Article rejected; don't send again", description: 'Transfer rejected; do not retry' }) }) .then(() => expect(caught).toBe(true)) }) test(`Example of sending an article to another site where the transfer fails:`, async () => { c('IHAVE ') s('335 Send it; end with .') const response = await client.ihave('') expect(response).toEqual({ code: 335, comment: 'Send it; end with .', description: 'Send article to be transferred', send: expect.any(Function) }) c( 'Path: pathost!demo!somewhere!not-for-mail', 'From: "Demo User" ', 'Newsgroups: misc.test', 'Subject: I am just a test article', 'Date: 6 Oct 1998 04:38:40 -0500', 'Organization: An Example Com, San Jose, CA', 'Message-ID: ', '', 'This is just a test article.', '.' ) s('436 Transfer failed') let caught = false return response .send({ headers: { Path: 'pathost!demo!somewhere!not-for-mail', From: '"Demo User" ', Newsgroups: 'misc.test', Subject: 'I am just a test article', Date: '6 Oct 1998 04:38:40 -0500', Organization: 'An Example Com, San Jose, CA', 'Message-ID': '' }, body: ['This is just a test article.'] }) .catch(response => { caught = true expect(response).toEqual({ code: 436, comment: 'Transfer failed', description: 'Transfer failed; try again later' }) }) .then(() => expect(caught).toBe(true)) }) test('Example of sending an article to a site that already has it:', async () => { c('IHAVE ') s('435 Duplicate') let caught = false return client .ihave('') .catch(response => { caught = true expect(response).toEqual({ code: 435, comment: 'Duplicate', description: 'Article not wanted' }) }) .then(() => expect(caught).toBe(true)) }) test(`Example of sending an article to a site that requests that the article be tried again later:`, async () => { c('IHAVE ') s('436 Retry later') let caught = false return client .ihave('') .catch(response => { caught = true expect(response).toEqual({ code: 436, comment: 'Retry later', description: 'Transfer not possible; try again later' }) }) .then(() => expect(caught).toBe(true)) }) }) describe('7.1.3. Examples', () => { test('examples', async () => { c('DATE') s('111 19990623135624') const response = await client.date() expect(response).toEqual({ code: 111, comment: '', isoDateTime: '1999-06-23T13:56:24.000Z', description: 'Server date and time' } as DateResponse) }) }) describe('7.2.3. Examples', () => { test('examples', async () => { c('HELP') s('100 Help text follows') s('This is some help text. There is no specific') s('formatting requirement for this test, though') s('it is customary for it to list the valid commands') s('and give a brief definition of what they do.') s('.') const response = await client.help() expect(response).toEqual({ code: 100, comment: 'Help text follows', description: 'Help text follows (multi-line)', text: [ 'This is some help text. There is no specific', 'formatting requirement for this test, though', 'it is customary for it to list the valid commands', 'and give a brief definition of what they do.' ] } as HelpResponse) }) }) describe('7.3.3. Examples', () => { test('Example where there are new groups:', async () => { c('NEWGROUPS 19990624 000000 GMT') s('231 list of new newsgroups follows') s('alt.rfc-writers.recovery 4 1 y') s('tx.natives.recovery 89 56 y') s('.') const response = await client.newgroups('1999-06-24T00:00:00Z') expect(response).toEqual({ code: 231, comment: 'list of new newsgroups follows', description: 'List of new newsgroups follows (multi-line)', newsgroups: [ { name: 'alt.rfc-writers.recovery', high: 4, low: 1, status: 'y' }, { name: 'tx.natives.recovery', high: 89, low: 56, status: 'y' } ] } as GroupsResponse) }) test('Example where there are no new groups:', async () => { c('NEWGROUPS 19990624 000000 GMT') s('231 list of new newsgroups follows') s('.') const response = await client.newgroups('1999-06-24T00:00:00Z') expect(response).toEqual({ code: 231, comment: 'list of new newsgroups follows', description: 'List of new newsgroups follows (multi-line)', newsgroups: [] } as GroupsResponse) }) }) describe('7.4.3. Examples', () => { test('Example where there are new articles:', async () => { c('NEWNEWS news.*,sci.* 19990624 000000 GMT') s('230 list of new articles by message-id follows') s('') s('') s('.') const response = await client.newnews('news.*,sci.*', '1999-06-24T00:00:00Z') expect(response).toEqual({ code: 230, comment: 'list of new articles by message-id follows', description: 'List of new articles follows (multi-line)', messageIds: ['', ''] } as NewnewsResponse) }) test('Example where there are no new articles:', async () => { c('NEWNEWS alt.* 19990624 000000 GMT') s('230 list of new articles by message-id follows') s('.') const response = await client.newnews('alt.*', '1999-06-24T00:00:00Z') expect(response).toEqual({ code: 230, comment: 'list of new articles by message-id follows', description: 'List of new articles follows (multi-line)', messageIds: [] } as NewnewsResponse) }) }) describe('7.5.1. Examples', () => { test('First session:', async () => { c('DATE') s('111 20010203112233') let response = await client.date() expect(response).toEqual({ code: 111, comment: '', isoDateTime: '2001-02-03T11:22:33.000Z', description: 'Server date and time' } as DateResponse) c('NEWNEWS local.chat 20001231 235959 GMT') s('230 list follows') s('') s('') s('') s('.') response = await client.newnews('local.chat', '2000-12-31T23:59:59Z') expect(response).toEqual({ code: 230, comment: 'list follows', description: 'List of new articles follows (multi-line)', messageIds: [ '', '', '' ] } as NewnewsResponse) }) test(`Second session (the client has subtracted 3 minutes from the timestamp returned previously):`, async () => { c('DATE') s('111 20010204003344') let response = await client.date() expect(response).toEqual({ code: 111, comment: '', isoDateTime: '2001-02-04T00:33:44.000Z', description: 'Server date and time' } as DateResponse) c('NEWNEWS local.chat 20010203 111933 GMT') s('230 list follows') s('') s('') s('') s('.') response = await client.newnews('local.chat', '2001-02-03T11:19:33Z') expect(response).toEqual({ code: 230, comment: 'list follows', description: 'List of new articles follows (multi-line)', messageIds: [ '', '', '' ] } as NewnewsResponse) }) }) describe('7.6.1.3. Examples', () => { test('Example of LIST with the ACTIVE keyword:', async () => { c('LIST ACTIVE') s('215 list of newsgroups follows') s('misc.test 3002322 3000234 y') s('comp.risks 442001 441099 m') s('alt.rfc-writers.recovery 4 1 y') s('tx.natives.recovery 89 56 y') s('tx.natives.recovery.d 11 9 n') s('.') const response = await client.listActive() expect(response).toEqual({ code: 215, comment: 'list of newsgroups follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'misc.test', high: 3002322, low: 3000234, status: 'y' }, { name: 'comp.risks', high: 442001, low: 441099, status: 'm' }, { name: 'alt.rfc-writers.recovery', high: 4, low: 1, status: 'y' }, { name: 'tx.natives.recovery', high: 89, low: 56, status: 'y' }, { name: 'tx.natives.recovery.d', high: 11, low: 9, status: 'n' } ] } as GroupsResponse) }) test('Example of LIST with no keyword:', async () => { c('LIST') s('215 list of newsgroups follows') s('misc.test 3002322 3000234 y') s('comp.risks 442001 441099 m') s('alt.rfc-writers.recovery 4 1 y') s('tx.natives.recovery 89 56 y') s('tx.natives.recovery.d 11 9 n') s('.') const response = await client.list() expect(response).toEqual({ code: 215, comment: 'list of newsgroups follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'misc.test', high: 3002322, low: 3000234, status: 'y' }, { name: 'comp.risks', high: 442001, low: 441099, status: 'm' }, { name: 'alt.rfc-writers.recovery', high: 4, low: 1, status: 'y' }, { name: 'tx.natives.recovery', high: 89, low: 56, status: 'y' }, { name: 'tx.natives.recovery.d', high: 11, low: 9, status: 'n' } ] } as GroupsResponse) }) test(`Example of LIST on a newsgroup-based keyword with and without wildmat:`, async () => { c('LIST ACTIVE.TIMES') s('215 information follows') s('misc.test 930445408 ') s('alt.rfc-writers.recovery 930562309 ') s('tx.natives.recovery 930678923 ') s('.') let response = await client.listActiveTimes() expect(response).toEqual({ code: 215, comment: 'information follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'misc.test', created: '1999-06-27T01:03:28.000Z', creator: '' }, { name: 'alt.rfc-writers.recovery', created: '1999-06-28T09:31:49.000Z', creator: '' }, { name: 'tx.natives.recovery', created: '1999-06-29T17:55:23.000Z', creator: '' } ] } as GroupsResponse) c('LIST ACTIVE.TIMES tx.*') s('215 information follows') s('tx.natives.recovery 930678923 ') s('.') response = await client.listActiveTimes('tx.*') expect(response).toEqual({ code: 215, comment: 'information follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'tx.natives.recovery', created: '1999-06-29T17:55:23.000Z', creator: '' } ] } as GroupsResponse) }) test(`Example of LIST returning an error where the keyword is recognized but the software does not maintain this information:`, async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('LIST ACTIVE NEWSGROUPS ACTIVE.TIMES XTRA.DATA') s('.') const response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], LIST: ['ACTIVE', 'NEWSGROUPS', 'ACTIVE.TIMES', 'XTRA.DATA'] } }) c('LIST XTRA.DATA') s('503 Data item not stored') let caught = false return client .command('LIST XTRA.DATA') .catch(response => { caught = true expect(response).toEqual({ code: 503, comment: 'Data item not stored', description: 'feature not supported' }) }) .then(() => expect(caught).toBe(true)) }) test('Example of LIST where the keyword is not recognised:', async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('LIST ACTIVE NEWSGROUPS ACTIVE.TIMES XTRA.DATA') s('.') const response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], LIST: ['ACTIVE', 'NEWSGROUPS', 'ACTIVE.TIMES', 'XTRA.DATA'] } }) c('LIST DISTRIB.PATS') s('501 Syntax Error') let caught = false return client .listDistribPats() .catch(response => { caught = true expect(response).toEqual({ code: 501, comment: 'Syntax Error', description: 'syntax error in command' }) }) .then(() => expect(caught).toBe(true)) }) test('For example:', async () => { c('LIST ACTIVE') s('215 list of newsgroups follows') s('misc.test 3002322 3000234 y') s('comp.risks 442001 441099 m') s('alt.rfc-writers.recovery 4 1 y') s('tx.natives.recovery 89 56 y') s('tx.natives.recovery.d 11 9 n') s('.') const response = await client.listActive() expect(response).toEqual({ code: 215, comment: 'list of newsgroups follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'misc.test', high: 3002322, low: 3000234, status: 'y' }, { name: 'comp.risks', high: 442001, low: 441099, status: 'm' }, { name: 'alt.rfc-writers.recovery', high: 4, low: 1, status: 'y' }, { name: 'tx.natives.recovery', high: 89, low: 56, status: 'y' }, { name: 'tx.natives.recovery.d', high: 11, low: 9, status: 'n' } ] } as GroupsResponse) }) test('or, on an implementation that includes leading zeroes:', async () => { c('LIST ACTIVE') s('215 list of newsgroups follows') s('misc.test 0003002322 0003000234 y') s('comp.risks 0000442001 0000441099 m') s('alt.rfc-writers.recovery 0000000004 0000000001 y') s('tx.natives.recovery 0000000089 0000000056 y') s('tx.natives.recovery.d 0000000011 0000000009 n') s('.') const response = await client.listActive() expect(response).toEqual({ code: 215, comment: 'list of newsgroups follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'misc.test', high: 3002322, low: 3000234, status: 'y' }, { name: 'comp.risks', high: 442001, low: 441099, status: 'm' }, { name: 'alt.rfc-writers.recovery', high: 4, low: 1, status: 'y' }, { name: 'tx.natives.recovery', high: 89, low: 56, status: 'y' }, { name: 'tx.natives.recovery.d', high: 11, low: 9, status: 'n' } ] } as GroupsResponse) }) test(`The information is newsgroup based, and a wildmat MAY be specified, in which case the response is limited to only the groups (if any) whose names match the wildmat. For example:`, async () => { c('LIST ACTIVE *.recovery') s('215 list of newsgroups follows') s('alt.rfc-writers.recovery 4 1 y') s('tx.natives.recovery 89 56 y') s('.') const response = await client.listActive('*.recovery') expect(response).toEqual({ code: 215, comment: 'list of newsgroups follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'alt.rfc-writers.recovery', high: 4, low: 1, status: 'y' }, { name: 'tx.natives.recovery', high: 89, low: 56, status: 'y' } ] } as GroupsResponse) }) }) describe('7.6.4. LIST ACTIVE.TIMES', () => { test(`The active.times list is maintained by some NNTP servers to contain information about who created a particular newsgroup and when. Each line of this list consists of three fields separated from each other by one or more spaces. The first field is the name of the newsgroup. The second is the time when this group was created on this news server, measured in seconds since the start of January 1, 1970. The third is plain text intended to describe the entity that created the newsgroup; it is often a mailbox as defined in RFC 2822 [RFC2822]. For example:`, async () => { c('LIST ACTIVE.TIMES') s('215 information follows') s('misc.test 930445408 ') s('alt.rfc-writers.recovery 930562309 ') s('tx.natives.recovery 930678923 ') s('.') const response = await client.listActiveTimes() expect(response).toEqual({ code: 215, comment: 'information follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'misc.test', created: '1999-06-27T01:03:28.000Z', creator: '' }, { name: 'alt.rfc-writers.recovery', created: '1999-06-28T09:31:49.000Z', creator: '' }, { name: 'tx.natives.recovery', created: '1999-06-29T17:55:23.000Z', creator: '' } ] } as GroupsResponse) }) }) describe('7.6.5. LIST DISTRIB.PATS', () => { test(`The distrib.pats list is maintained by some NNTP servers to assist clients to choose a value for the content of the Distribution header of a news article being posted. Each line of this list consists of three fields separated from each other by a colon (":"). The first field is a weight, the second field is a wildmat (which may be a simple newsgroup name), and the third field is a value for the Distribution header content. For example:`, async () => { c('LIST DISTRIB.PATS') s('215 information follows') s('10:local.*:local') s('5:*:world') s('20:local.here.*:thissite') s('.') const response = await client.listDistribPats() expect(response).toEqual({ code: 215, comment: 'information follows', description: 'Information follows (multi-line)', distributionPatterns: [ { weight: 10, wildmat: 'local.*', distributionHeader: 'local' }, { weight: 5, wildmat: '*', distributionHeader: 'world' }, { weight: 20, wildmat: 'local.here.*', distributionHeader: 'thissite' } ] } as DistributionPatternsResponse) }) }) describe('7.6.6. LIST NEWSGROUPS', () => { test(`The newsgroups list is maintained by NNTP servers to contain the name of each newsgroup that is available on the server and a short description about the purpose of the group. Each line of this list consists of two fields separated from each other by one or more space or TAB characters (the usual practice is a single TAB). The first field is the name of the newsgroup, and the second is a short description of the group. For example:`, async () => { c('LIST NEWSGROUPS') s('215 information follows') s('misc.test General Usenet testing') s('alt.rfc-writers.recovery RFC Writers Recovery') s('tx.natives.recovery Texas Natives Recovery') s('.') const response = await client.listNewsgroups() expect(response).toEqual({ code: 215, comment: 'information follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'misc.test', description: 'General Usenet testing' }, { name: 'alt.rfc-writers.recovery', description: 'RFC Writers Recovery' }, { name: 'tx.natives.recovery', description: 'Texas Natives Recovery' } ] } as GroupsResponse) }) }) describe('7.6.6. LIST NEWSGROUPS some newsgroups without description', () => { test(`The newsgroups list is maintained by NNTP servers to contain the name of each newsgroup that is available on the server and a short description about the purpose of the group. Each line of this list consists of two fields separated from each other by one or more space or TAB characters (the usual practice is a single TAB). The first field is the name of the newsgroup, and the second is a short description of the group. For example:`, async () => { c('LIST NEWSGROUPS') s('215 information follows') s('misc.test ') s('alt.rfc-writers.recovery') s('tx.natives.recovery Texas Natives Recovery') s('.') const response = await client.listNewsgroups() expect(response).toEqual({ code: 215, comment: 'information follows', description: 'Information follows (multi-line)', newsgroups: [ { name: 'misc.test', description: '' }, { name: 'alt.rfc-writers.recovery', description: '' }, { name: 'tx.natives.recovery', description: 'Texas Natives Recovery' } ] } as GroupsResponse) }) }) describe('8.3.3. Examples', () => { test(`Example of a successful retrieval of overview information for an article (explicitly not using an article number):`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('OVER') s('224 Overview information follows') s( '3000234|I am just a test article|"Demo User"' + '|6 Oct 1998 04:38:40 -0500|' + '<45223423@example.com>|<45454@example.net>|1234|' + '17|Xref: news.example.com misc.test:3000363' ) s('.') response = await client.over() expect(response).toEqual({ code: 224, comment: 'Overview information follows', description: 'Overview information follows (multi-line)', articles: [ { articleNumber: 3000234, headers: { SUBJECT: 'I am just a test article', FROM: '"Demo User"', DATE: '6 Oct 1998 04:38:40 -0500', 'MESSAGE-ID': '<45223423@example.com>', REFERENCES: '<45454@example.net>', XREF: 'news.example.com misc.test:3000363' }, metadata: { ':bytes': 1234, ':lines': 17 } } ] }) }) test(`Example of a successful retrieval of overview information for an article by message-id:`, async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('OVER MSGID') s('LIST ACTIVE NEWSGROUPS OVERVIEW.FMT') s('.') let response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], OVER: ['MSGID'], LIST: ['ACTIVE', 'NEWSGROUPS', 'OVERVIEW.FMT'] } }) c('OVER <45223423@example.com>') s('224 Overview information follows') s( '0|I am just a test article|"Demo User"' + '|6 Oct 1998 04:38:40 -0500|' + '<45223423@example.com>|<45454@example.net>|1234|' + '17|Xref: news.example.com misc.test:3000363' ) s('.') response = await client.over('<45223423@example.com>') expect(response).toEqual({ code: 224, comment: 'Overview information follows', description: 'Overview information follows (multi-line)', articles: [ { articleNumber: 0, headers: { SUBJECT: 'I am just a test article', FROM: '"Demo User"', DATE: '6 Oct 1998 04:38:40 -0500', 'MESSAGE-ID': '<45223423@example.com>', REFERENCES: '<45454@example.net>', XREF: 'news.example.com misc.test:3000363' }, metadata: { ':bytes': 1234, ':lines': 17 } } ] }) }) test(`Example of the same commands on a system that does not implement retrieval by message-id:`, async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('OVER') s('LIST ACTIVE NEWSGROUPS OVERVIEW.FMT') s('.') const response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], OVER: [], LIST: ['ACTIVE', 'NEWSGROUPS', 'OVERVIEW.FMT'] } }) c('OVER <45223423@example.com>') s('503 Overview by message-id unsupported') let caught = false return client .over('<45223423@example.com>') .catch(response => { caught = true expect(response).toEqual({ code: 503, comment: 'Overview by message-id unsupported', description: 'feature not supported' }) }) .then(() => expect(caught).toBe(true)) }) test(`Example of a successful retrieval of overview information for a range of articles:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { name: 'misc.test', number: 1234, low: 3000234, high: 3002322 } } as GroupResponse) c('OVER 3000234-3000240') s('224 Overview information follows') s( '3000234|I am just a test article|"Demo User"' + '|6 Oct 1998 04:38:40 -0500|' + '<45223423@example.com>|<45454@example.net>|1234|' + '17|Xref: news.example.com misc.test:3000363' ) s( '3000235|Another test article|nobody@nowhere.to' + '(Demo User)|6 Oct 1998 04:38:45 -0500|<45223425@to.to>||' + '4818|37||Distribution: fi' ) // TODO: RFC must be confused, || can't be an empty XREF header.. >:( s( '3000238|Re: I am just a test article|somebody@elsewhere.to|' + '7 Oct 1998 11:38:40 +1200||' + '<45223423@to.to>|9234|51' ) s('.') response = await client.over({ start: 3000234, end: 3000240 }) expect(response).toEqual({ code: 224, comment: 'Overview information follows', description: 'Overview information follows (multi-line)', articles: [ { articleNumber: 3000234, headers: { SUBJECT: 'I am just a test article', FROM: '"Demo User"', DATE: '6 Oct 1998 04:38:40 -0500', 'MESSAGE-ID': '<45223423@example.com>', REFERENCES: '<45454@example.net>', XREF: 'news.example.com misc.test:3000363' }, metadata: { ':bytes': 1234, ':lines': 17 } }, { articleNumber: 3000235, headers: { SUBJECT: 'Another test article', FROM: 'nobody@nowhere.to(Demo User)', DATE: '6 Oct 1998 04:38:45 -0500', 'MESSAGE-ID': '<45223425@to.to>', REFERENCES: '', DISTRIBUTION: 'fi' }, metadata: { ':bytes': 4818, ':lines': 37 } }, { articleNumber: 3000238, headers: { SUBJECT: 'Re: I am just a test article', FROM: 'somebody@elsewhere.to', DATE: '7 Oct 1998 11:38:40 +1200', 'MESSAGE-ID': '', REFERENCES: '<45223423@to.to>' }, metadata: { ':bytes': 9234, ':lines': 51 } } ] }) }) test(`Example of an unsuccessful retrieval of overview information on an article by number:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('OVER 300256') s('423 No such article in this group') let caught = false return client .over(300256) .catch(response => { caught = true expect(response).toEqual({ code: 423, comment: 'No such article in this group', description: 'No articles in that range' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test('Example of an invalid range:', async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('OVER 3000444-3000222') s('423 Empty range') let caught = false return client .over({ start: 3000444, end: 3000222 }) .catch(response => { caught = true expect(response).toEqual({ code: 423, comment: 'Empty range', description: 'No articles in that range' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test(`Example of an unsuccessful retrieval of overview information by number because no newsgroup was selected first:`, async () => { // [Assumes currently selected newsgroup is invalid.] c('OVER') s('412 No newsgroup selected') let caught = false return client .over() .catch(response => { caught = true expect(response).toEqual({ code: 412, comment: 'No newsgroup selected', description: 'No newsgroup selected' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) test(`Example of an attempt to retrieve information when the currently selected newsgroup is empty:`, async () => { c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') const response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } } as GroupResponse) c('OVER') s('420 No current article selected') let caught = false return client .over() .catch(response => { caught = true expect(response).toEqual({ code: 420, comment: 'No current article selected', description: 'Current article number is invalid' } as NntpErrorResponse) }) .then(() => expect(caught).toBe(true)) }) }) describe('8.4.3. Examples', () => { test(`Example of LIST OVERVIEW.FMT output corresponding to the example OVER output above, in the preferred format:`, async () => { c('LIST OVERVIEW.FMT') s('215 Order of fields in overview database.') s('Subject:') s('From:') s('Date:') s('Message-ID:') s('References:') s(':bytes') s(':lines') s('Xref:full') s('Distribution:full') s('.') const response = await client.listOverviewFmt() expect(response).toEqual({ code: 215, comment: 'Order of fields in overview database.', description: 'Information follows (multi-line)', headerFields: ['SUBJECT', 'FROM', 'DATE', 'MESSAGE-ID', 'REFERENCES', 'XREF', 'DISTRIBUTION'], metadataFields: [':bytes', ':lines'] } as OverviewFormatResponse) }) test(`Example of LIST OVERVIEW.FMT output corresponding to the example OVER output above, in the alternative format:`, async () => { c('LIST OVERVIEW.FMT') s('215 Order of fields in overview database.') s('Subject:') s('From:') s('Date:') s('Message-ID:') s('References:') s('Bytes:') s('Lines:') s('Xref:FULL') s('Distribution:FULL') s('.') const response = await client.listOverviewFmt() expect(response).toEqual({ code: 215, comment: 'Order of fields in overview database.', description: 'Information follows (multi-line)', headerFields: ['SUBJECT', 'FROM', 'DATE', 'MESSAGE-ID', 'REFERENCES', 'XREF', 'DISTRIBUTION'], metadataFields: [':bytes', ':lines'] } as OverviewFormatResponse) }) }) describe('8.5.3. Examples', () => { test(`Example of a successful retrieval of subject lines from a range of articles (3000235 has no Subject header, and 3000236 is missing):`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('HDR Subject 3000234-3000238') s('225 Headers follow') s('3000234 I am just a test article') s('3000235') s('3000237 Re: I am just a test article') s('3000238 Ditto') s('.') response = await client.hdr('Subject', { start: 3000234, end: 3000238 }) expect(response).toEqual({ code: 225, comment: 'Headers follow', description: 'Headers follow (multi-line)', articles: [ { articleNumber: 3000234, fieldContents: 'I am just a test article' }, { articleNumber: 3000235, fieldContents: '' }, { articleNumber: 3000237, fieldContents: 'Re: I am just a test article' }, { articleNumber: 3000238, fieldContents: 'Ditto' } ] } as ArticlesResponse) }) test(`Example of a successful retrieval of line counts from a range of articles:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('HDR :lines 3000234-3000238') s('225 Headers follow') s('3000234 42') s('3000235 5') s('3000237 11') s('3000238 2378') s('.') response = await client.hdr(':lines', { start: 3000234, end: 3000238 }) expect(response).toEqual({ code: 225, comment: 'Headers follow', description: 'Headers follow (multi-line)', articles: [ { articleNumber: 3000234, fieldContents: '42' }, { articleNumber: 3000235, fieldContents: '5' }, { articleNumber: 3000237, fieldContents: '11' }, { articleNumber: 3000238, fieldContents: '2378' } ] } as ArticlesResponse) }) test(`Example of a successful retrieval of the subject line from an article by message-id:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('HDR subject ') s('225 Header information follows') s('0 I am just a test article') s('.') response = await client.hdr('subject', '') expect(response).toEqual({ code: 225, comment: 'Header information follows', description: 'Headers follow (multi-line)', articles: [ { articleNumber: 0, fieldContents: 'I am just a test article' } ] } as ArticlesResponse) }) test(`Example of a successful retrieval of the subject line from the current article:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') let response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('HDR subject') s('225 Header information follows') s('3000234 I am just a test article') s('.') response = await client.hdr('subject') expect(response).toEqual({ code: 225, comment: 'Header information follows', description: 'Headers follow (multi-line)', articles: [ { articleNumber: 3000234, fieldContents: 'I am just a test article' } ] } as ArticlesResponse) }) test(`Example of an unsuccessful retrieval of a header from an article by message-id:`, async () => { c('HDR subject ') s('430 No Such Article Found') expect.assertions(2) return client.hdr('subject', '').catch(response => { expect(response).toEqual({ code: 430, comment: 'No Such Article Found', description: 'No article with that message-id' } as NntpErrorResponse) }) }) test(`Example of an unsuccessful retrieval of headers from articles by number because no newsgroup was selected first:`, async () => { // [Assumes currently selected newsgroup is invalid.] c('HDR subject 300256-') s('412 No newsgroup selected') expect.assertions(2) return client.hdr('subject', { start: 300256 }).catch(response => { expect(response).toEqual({ code: 412, comment: 'No newsgroup selected', description: 'No newsgroup selected' } as NntpErrorResponse) }) }) test(`Example of an unsuccessful retrieval of headers because the currently selected newsgroup is empty:`, async () => { c('GROUP example.empty.newsgroup') s('211 0 0 0 example.empty.newsgroup') const response = await client.group('example.empty.newsgroup') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 0, low: 0, high: 0, name: 'example.empty.newsgroup' } } as GroupResponse) c('HDR subject 1-') s('423 No articles in that range') expect.assertions(4) return client.hdr('subject', { start: 1 }).catch(response => { expect(response).toEqual({ code: 423, comment: 'No articles in that range', description: 'No articles in that range' } as NntpErrorResponse) }) }) test(`Example of an unsuccessful retrieval of headers because the server does not allow HDR commands for that header:`, async () => { c('GROUP misc.test') s('211 1234 3000234 3002322 misc.test') const response = await client.group('misc.test') expect(response).toEqual({ code: 211, comment: '', description: 'Group successfully selected', group: { number: 1234, low: 3000234, high: 3002322, name: 'misc.test' } } as GroupResponse) c('HDR Content-Type 3000234-3000238') s('503 HDR not permitted on Content-Type') expect.assertions(4) return client.hdr('Content-Type', { start: 3000234, end: 3000238 }).catch(response => { expect(response).toEqual({ code: 503, comment: 'HDR not permitted on Content-Type', description: 'feature not supported' } as NntpErrorResponse) }) }) }) describe('8.6.3. Examples', () => { test('Example of an implementation providing access to only a few headers:', async () => { c('LIST HEADERS') s('215 headers supported:') s('Subject') s('Message-ID') s('Xref') s('.') const response = await client.listHeaders() expect(response).toEqual({ code: 215, comment: 'headers supported:', description: 'Field list follows (multi-line)', fields: ['SUBJECT', 'MESSAGE-ID', 'XREF'] } as ListHeadersResponse) }) test(`Example of an implementation providing access to the same fields as the first example in Section 8.4.3:`, async () => { c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('OVER') s('HDR') s('LIST ACTIVE NEWSGROUPS HEADERS OVERVIEW.FMT') s('.') let response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], OVER: [], HDR: [], LIST: ['ACTIVE', 'NEWSGROUPS', 'HEADERS', 'OVERVIEW.FMT'] } } as CapabilitiesResponse) c('LIST HEADERS') s('215 headers and metadata items supported:') s('Date') s('Distribution') s('From') s('Message-ID') s('References') s('Subject') s('Xref') s(':bytes') s(':lines') s('.') response = await client.listHeaders() expect(response).toEqual({ code: 215, comment: 'headers and metadata items supported:', description: 'Field list follows (multi-line)', fields: [ 'DATE', 'DISTRIBUTION', 'FROM', 'MESSAGE-ID', 'REFERENCES', 'SUBJECT', 'XREF', ':bytes', ':lines' ] } as ListHeadersResponse) }) test('Example of an implementation providing access to all headers:', async () => { c('LIST HEADERS') s('215 metadata items supported:') s(':') s(':lines') s(':bytes') s(':x-article-number') s('.') const response = await client.listHeaders() expect(response).toEqual({ code: 215, comment: 'metadata items supported:', description: 'Field list follows (multi-line)', fields: [':', ':lines', ':bytes', ':x-article-number'] } as ListHeadersResponse) }) test(`Example of an implementation distinguishing the first form of the HDR command from the other two forms:`, async () => { c('LIST HEADERS RANGE') s('215 metadata items supported:') s(':') s(':lines') s(':bytes') s('.') let response = await client.listHeaders('RANGE') expect(response).toEqual({ code: 215, comment: 'metadata items supported:', description: 'Field list follows (multi-line)', fields: [':', ':lines', ':bytes'] } as ListHeadersResponse) c('LIST HEADERS MSGID') s('215 headers and metadata items supported:') s('Date') s('Distribution') s('From') s('Message-ID') s('References') s('Subject') s(':lines') s(':bytes') s(':x-article-number') s('.') response = await client.listHeaders('MSGID') expect(response).toEqual({ code: 215, comment: 'headers and metadata items supported:', description: 'Field list follows (multi-line)', fields: [ 'DATE', 'DISTRIBUTION', 'FROM', 'MESSAGE-ID', 'REFERENCES', 'SUBJECT', ':lines', ':bytes', ':x-article-number' ] } as ListHeadersResponse) c('LIST HEADERS') s('215 headers and metadata items supported:') s('Date') s('Distribution') s('From') s('Message-ID') s('References') s('Subject') s(':lines') s(':bytes') s('.') response = await client.listHeaders() expect(response).toEqual({ code: 215, comment: 'headers and metadata items supported:', description: 'Field list follows (multi-line)', fields: [ 'DATE', 'DISTRIBUTION', 'FROM', 'MESSAGE-ID', 'REFERENCES', 'SUBJECT', ':lines', ':bytes' ] } as ListHeadersResponse) }) }) describe('12.6. Caching of Capability Lists', () => { test(`For example, consider a server that permits the use of cleartext passwords on links that are encrypted but not otherwise:`, async () => { // [Initial connection set-up completed.] s('200 NNTP Service Ready, posting permitted') c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('NEWNEWS') s('POST') s('XENCRYPT') s('LIST ACTIVE NEWSGROUPS') s('.') let response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], NEWNEWS: [], POST: [], XENCRYPT: [], LIST: ['ACTIVE', 'NEWSGROUPS'] } }) c('XENCRYPT') // [Client and server negotiate encryption on the link] s('283 Encrypted link established', server.upgradeTls) // @ts-expect-error handlers.addHandler('XENCRYPT', 283, handlers.build('Encryption success')) response = await client.command('XENCRYPT') expect(response).toEqual({ code: 283, comment: 'Encrypted link established', description: 'Encryption success' } as NntpResponse) const socket = await client._connection.upgradeTls() expect(socket).toEqual(expect.any(tls.TLSSocket)) c('CAPABILITIES') s('101 Capability list:') s('VERSION 2') s('READER') s('NEWNEWS') s('POST') s('XSECRET') s('LIST ACTIVE NEWSGROUPS') s('.') // NOTE: client closes connection immediately on errors, test can't pass response = await client.capabilities() expect(response).toEqual({ code: 101, comment: 'Capability list:', description: 'Capability list follows (multi-line)', capabilities: { VERSION: ['2'], READER: [], NEWNEWS: [], POST: [], XSECRET: [], LIST: ['ACTIVE', 'NEWSGROUPS'] } }) c('XSECRET fred flintstone') s('290 Password for fred accepted') // @ts-expect-error handlers.addHandler('XSECRET', 290, handlers.build('Login success')) response = await client.command('XSECRET', 'fred', 'flintstone') expect(response).toEqual({ code: 290, comment: 'Password for fred accepted', description: 'Login success' } as NntpResponse) }) test.skip(`If the client caches the last capabilities list, then on the next session it will attempt to use XSECRET on an unencrypted link:`, async () => { // [Initial connection set-up completed.] s('200 NNTP Service Ready, posting permitted') c('XSECRET fred flintstone') s('483 Only permitted on secure links') try { await client.command('XSECRET', 'fred', 'flintstone') } catch (response) { expect(response).toEqual({ code: 483, comment: 'Only permitted on secure links', description: '???' } as NntpErrorResponse) } }) }) describe('A.2. Message-IDs', () => { test(`Note that, because the message-id might not have been derived from the Message-ID header in the article, the following example is legitimate (though unusual):`, async () => { c('HEAD <45223423@example.com>') s('221 0 <45223423@example.com>') s('Path: pathost!demo!whitehouse!not-for-mail') s('Message-ID: <1234@example.net>') s('From: "Demo User" ') s('Newsgroups: misc.test') s('Subject: I am just a test article') s('Date: 6 Oct 1998 04:38:40 -0500') s('Organization: An Example Net, Uncertain, Texas') s('.') const response = await client.head('<45223423@example.com>') expect(response).toEqual({ code: 221, comment: '', description: 'Headers follow (multi-line)', article: { articleNumber: 0, messageId: '<45223423@example.com>', headers: { PATH: 'pathost!demo!whitehouse!not-for-mail', FROM: '"Demo User" ', NEWSGROUPS: 'misc.test', SUBJECT: 'I am just a test article', DATE: '6 Oct 1998 04:38:40 -0500', ORGANIZATION: 'An Example Net, Uncertain, Texas', 'MESSAGE-ID': '<1234@example.net>' } } } as ArticleResponse) }) })