aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorST-DDT <[email protected]>2024-10-10 18:14:30 +0200
committerGitHub <[email protected]>2024-10-10 18:14:30 +0200
commita5a6c5b4e1145b1e21e5d8e540bc6f55f97d3a84 (patch)
tree085617f44738e2e262bb958ffa00ae17e703c1cf
parent5b1c8588f8a57be712e64434f7b17a8407a4f465 (diff)
downloadfaker-a5a6c5b4e1145b1e21e5d8e540bc6f55f97d3a84.tar.xz
faker-a5a6c5b4e1145b1e21e5d8e540bc6f55f97d3a84.zip
feat(internet): improve ipv4 method (#2992)
-rw-r--r--src/index.ts3
-rw-r--r--src/modules/internet/index.ts170
-rw-r--r--test/modules/__snapshots__/internet.spec.ts.snap22
-rw-r--r--test/modules/internet.spec.ts98
4 files changed, 282 insertions, 11 deletions
diff --git a/src/index.ts b/src/index.ts
index bef353ce..a8c4ecea 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -67,7 +67,8 @@ export type { GitModule } from './modules/git';
export type { HackerModule } from './modules/hacker';
export type { HelpersModule, SimpleHelpersModule } from './modules/helpers';
export type { ImageModule } from './modules/image';
-export type { InternetModule } from './modules/internet';
+export { IPv4Network } from './modules/internet';
+export type { IPv4NetworkType, InternetModule } from './modules/internet';
export type { LocationModule } from './modules/location';
export type { LoremModule } from './modules/lorem';
export type { MusicModule } from './modules/music';
diff --git a/src/modules/internet/index.ts b/src/modules/internet/index.ts
index f6884d88..a455135b 100644
--- a/src/modules/internet/index.ts
+++ b/src/modules/internet/index.ts
@@ -1,3 +1,4 @@
+import { FakerError } from '../../errors/faker-error';
import { ModuleBase } from '../../internal/module-base';
import { charMapping } from './char-mappings';
import * as random_ua from './user-agent';
@@ -23,6 +24,82 @@ export type HTTPStatusCodeType =
export type HTTPProtocolType = 'http' | 'https';
+export enum IPv4Network {
+ /**
+ * Equivalent to: `0.0.0.0/0`
+ */
+ Any = 'any',
+ /**
+ * Equivalent to: `127.0.0.0/8`
+ *
+ * @see [RFC1122](https://www.rfc-editor.org/rfc/rfc1122)
+ */
+ Loopback = 'loopback',
+ /**
+ * Equivalent to: `10.0.0.0/8`
+ *
+ * @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
+ */
+ PrivateA = 'private-a',
+ /**
+ * Equivalent to: `172.16.0.0/12`
+ *
+ * @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
+ */
+ PrivateB = 'private-b',
+ /**
+ * Equivalent to: `192.168.0.0/16`
+ *
+ * @see [RFC1918](https://www.rfc-editor.org/rfc/rfc1918)
+ */
+ PrivateC = 'private-c',
+ /**
+ * Equivalent to: `192.0.2.0/24`
+ *
+ * @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
+ */
+ TestNet1 = 'test-net-1',
+ /**
+ * Equivalent to: `198.51.100.0/24`
+ *
+ * @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
+ */
+ TestNet2 = 'test-net-2',
+ /**
+ * Equivalent to: `203.0.113.0/24`
+ *
+ * @see [RFC5737](https://www.rfc-editor.org/rfc/rfc5737)
+ */
+ TestNet3 = 'test-net-3',
+ /**
+ * Equivalent to: `169.254.0.0/16`
+ *
+ * @see [RFC3927](https://www.rfc-editor.org/rfc/rfc3927)
+ */
+ LinkLocal = 'link-local',
+ /**
+ * Equivalent to: `224.0.0.0/4`
+ *
+ * @see [RFC5771](https://www.rfc-editor.org/rfc/rfc5771)
+ */
+ Multicast = 'multicast',
+}
+
+export type IPv4NetworkType = `${IPv4Network}`;
+
+const ipv4Networks: Record<IPv4Network, string> = {
+ [IPv4Network.Any]: '0.0.0.0/0',
+ [IPv4Network.Loopback]: '127.0.0.0/8',
+ [IPv4Network.PrivateA]: '10.0.0.0/8',
+ [IPv4Network.PrivateB]: '172.16.0.0/12',
+ [IPv4Network.PrivateC]: '192.168.0.0/16',
+ [IPv4Network.TestNet1]: '192.0.2.0/24',
+ [IPv4Network.TestNet2]: '198.51.100.0/24',
+ [IPv4Network.TestNet3]: '203.0.113.0/24',
+ [IPv4Network.LinkLocal]: '169.254.0.0/16',
+ [IPv4Network.Multicast]: '224.0.0.0/4',
+};
+
/**
* Module to generate internet related entries.
*
@@ -485,15 +562,100 @@ export class InternetModule extends ModuleBase {
/**
* Generates a random IPv4 address.
*
+ * @param options The optional options object.
+ * @param options.cidrBlock The optional CIDR block to use. Must be in the format `x.x.x.x/y`. Defaults to `'0.0.0.0/0'`.
+ *
* @example
* faker.internet.ipv4() // '245.108.222.0'
+ * faker.internet.ipv4({ cidrBlock: '192.168.0.0/16' }) // '192.168.215.224'
*
* @since 6.1.1
*/
- ipv4(): string {
- return Array.from({ length: 4 }, () => this.faker.number.int(255)).join(
- '.'
- );
+ ipv4(options?: {
+ /**
+ * The optional CIDR block to use. Must be in the format `x.x.x.x/y`.
+ *
+ * @default '0.0.0.0/0'
+ */
+ cidrBlock?: string;
+ }): string;
+ /**
+ * Generates a random IPv4 address.
+ *
+ * @param options The optional options object.
+ * @param options.network The optional network to use. This is intended as an alias for well-known `cidrBlock`s. Defaults to `'any'`.
+ *
+ * @example
+ * faker.internet.ipv4() // '245.108.222.0'
+ * faker.internet.ipv4({ network: 'private-a' }) // '10.199.154.205'
+ *
+ * @since 6.1.1
+ */
+ ipv4(options?: {
+ /**
+ * The optional network to use. This is intended as an alias for well-known `cidrBlock`s.
+ *
+ * @default 'any'
+ */
+ network?: IPv4NetworkType;
+ }): string;
+ /**
+ * Generates a random IPv4 address.
+ *
+ * @param options The optional options object.
+ * @param options.cidrBlock The optional CIDR block to use. Must be in the format `x.x.x.x/y`. Defaults to `'0.0.0.0/0'`.
+ * @param options.network The optional network to use. This is intended as an alias for well-known `cidrBlock`s. Defaults to `'any'`.
+ *
+ * @example
+ * faker.internet.ipv4() // '245.108.222.0'
+ * faker.internet.ipv4({ cidrBlock: '192.168.0.0/16' }) // '192.168.215.224'
+ * faker.internet.ipv4({ network: 'private-a' }) // '10.199.154.205'
+ *
+ * @since 6.1.1
+ */
+ ipv4(
+ options?:
+ | {
+ /**
+ * The optional CIDR block to use. Must be in the format `x.x.x.x/y`.
+ *
+ * @default '0.0.0.0/0'
+ */
+ cidrBlock?: string;
+ }
+ | {
+ /**
+ * The optional network to use. This is intended as an alias for well-known `cidrBlock`s.
+ *
+ * @default 'any'
+ */
+ network?: IPv4NetworkType;
+ }
+ ): string;
+ ipv4(
+ options: { cidrBlock?: string; network?: IPv4NetworkType } = {}
+ ): string {
+ const { network = 'any', cidrBlock = ipv4Networks[network] } = options;
+
+ if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/.test(cidrBlock)) {
+ throw new FakerError(
+ `Invalid CIDR block provided: ${cidrBlock}. Must be in the format x.x.x.x/y.`
+ );
+ }
+
+ const [ipText, subnet] = cidrBlock.split('/');
+ const subnetMask = 0xffffffff >>> Number.parseInt(subnet);
+ const [rawIp1, rawIp2, rawIp3, rawIp4] = ipText.split('.').map(Number);
+ const rawIp = (rawIp1 << 24) | (rawIp2 << 16) | (rawIp3 << 8) | rawIp4;
+ const networkIp = rawIp & ~subnetMask;
+ const hostOffset = this.faker.number.int(subnetMask);
+ const ip = networkIp | hostOffset;
+ return [
+ (ip >>> 24) & 0xff,
+ (ip >>> 16) & 0xff,
+ (ip >>> 8) & 0xff,
+ ip & 0xff,
+ ].join('.');
}
/**
diff --git a/test/modules/__snapshots__/internet.spec.ts.snap b/test/modules/__snapshots__/internet.spec.ts.snap
index bb330fbd..9ec2d265 100644
--- a/test/modules/__snapshots__/internet.spec.ts.snap
+++ b/test/modules/__snapshots__/internet.spec.ts.snap
@@ -64,9 +64,13 @@ exports[`internet > 42 > httpStatusCode > noArgs 1`] = `226`;
exports[`internet > 42 > httpStatusCode > with options 1`] = `410`;
-exports[`internet > 42 > ip 1`] = `"243.187.153.39"`;
+exports[`internet > 42 > ip 1`] = `"243.98.3.69"`;
-exports[`internet > 42 > ipv4 1`] = `"95.243.187.153"`;
+exports[`internet > 42 > ipv4 > noArgs 1`] = `"95.225.220.121"`;
+
+exports[`internet > 42 > ipv4 > with cidrBlock 1`] = `"192.168.13.95"`;
+
+exports[`internet > 42 > ipv4 > with network 1`] = `"229.254.29.199"`;
exports[`internet > 42 > ipv6 1`] = `"8ead:331d:df0f:c444:6b96:d368:ab4b:d1d3"`;
@@ -182,7 +186,11 @@ exports[`internet > 1211 > httpStatusCode > with options 1`] = `429`;
exports[`internet > 1211 > ip 1`] = `"d4fe:fa7f:baec:9dc4:c48f:a8eb:f46f:b7c8"`;
-exports[`internet > 1211 > ipv4 1`] = `"237.228.57.255"`;
+exports[`internet > 1211 > ipv4 > noArgs 1`] = `"237.179.127.46"`;
+
+exports[`internet > 1211 > ipv4 > with cidrBlock 1`] = `"192.168.13.237"`;
+
+exports[`internet > 1211 > ipv4 > with network 1`] = `"238.219.55.242"`;
exports[`internet > 1211 > ipv6 1`] = `"ed4f:efa7:fbae:c9dc:4c48:fa8e:bf46:fb7c"`;
@@ -296,9 +304,13 @@ exports[`internet > 1337 > httpStatusCode > noArgs 1`] = `201`;
exports[`internet > 1337 > httpStatusCode > with options 1`] = `407`;
-exports[`internet > 1337 > ip 1`] = `"40.71.117.82"`;
+exports[`internet > 1337 > ip 1`] = `"40.159.131.70"`;
+
+exports[`internet > 1337 > ipv4 > noArgs 1`] = `"67.20.12.145"`;
+
+exports[`internet > 1337 > ipv4 > with cidrBlock 1`] = `"192.168.13.67"`;
-exports[`internet > 1337 > ipv4 1`] = `"67.40.71.117"`;
+exports[`internet > 1337 > ipv4 > with network 1`] = `"228.49.64.201"`;
exports[`internet > 1337 > ipv6 1`] = `"536a:7b5f:a28d:2f9b:b79c:a46e:a394:bc4f"`;
diff --git a/test/modules/internet.spec.ts b/test/modules/internet.spec.ts
index 25c5150f..04b255fd 100644
--- a/test/modules/internet.spec.ts
+++ b/test/modules/internet.spec.ts
@@ -1,6 +1,8 @@
import validator from 'validator';
import { describe, expect, it } from 'vitest';
import { allFakers, faker } from '../../src';
+import { FakerError } from '../../src/errors/faker-error';
+import { IPv4Network } from '../../src/modules/internet';
import { seededTests } from '../support/seeded-runs';
import { times } from './../support/times';
@@ -15,7 +17,6 @@ describe('internet', () => {
'domainSuffix',
'domainWord',
'ip',
- 'ipv4',
'ipv6',
'port',
'userAgent'
@@ -133,6 +134,12 @@ describe('internet', () => {
protocol: 'http',
});
});
+
+ t.describe('ipv4', (t) => {
+ t.it('noArgs')
+ .it('with cidrBlock', { cidrBlock: '192.168.13.37/24' })
+ .it('with network', { network: IPv4Network.Multicast });
+ });
});
describe.each(times(NON_SEEDED_BASED_RUN).map(() => faker.seed()))(
@@ -595,6 +602,95 @@ describe('internet', () => {
expect(+part).toBeLessThanOrEqual(255);
}
});
+
+ it('should return a random IPv4 for a given CIDR block', () => {
+ const actual = faker.internet.ipv4({
+ cidrBlock: '192.168.42.255/24',
+ });
+
+ expect(actual).toBeTruthy();
+ expect(actual).toBeTypeOf('string');
+ expect(actual).toSatisfy((value: string) => validator.isIP(value, 4));
+ expect(actual).toMatch(/^192\.168\.42\.\d{1,3}$/);
+ });
+
+ it('should return a random IPv4 for a given CIDR block non-8ish network mask', () => {
+ const actual = faker.internet.ipv4({
+ cidrBlock: '192.168.0.255/20',
+ });
+
+ expect(actual).toBeTruthy();
+ expect(actual).toBeTypeOf('string');
+ expect(actual).toSatisfy((value: string) => validator.isIP(value, 4));
+
+ const [first, second, third, fourth] = actual.split('.').map(Number);
+ expect(first).toBe(192);
+ expect(second).toBe(168);
+ expect(third).toBeGreaterThanOrEqual(0);
+ expect(third).toBeLessThanOrEqual(15);
+ expect(fourth).toBeGreaterThanOrEqual(0);
+ expect(fourth).toBeLessThanOrEqual(255);
+ });
+
+ it.each([
+ '',
+ '...',
+ '.../',
+ '.0.0.0/0',
+ '0..0.0/0',
+ '0.0..0/0',
+ '0.0.0./0',
+ '0.0.0.0/',
+ 'a.0.0.0/0',
+ '0.b.0.0/0',
+ '0.0.c.0/0',
+ '0.0.0.d/0',
+ '0.0.0.0/e',
+ ])(
+ 'should throw an error if not following the x.x.x.x/y format',
+ (cidrBlock) => {
+ expect(() =>
+ faker.internet.ipv4({
+ cidrBlock,
+ })
+ ).toThrow(
+ new FakerError(
+ `Invalid CIDR block provided: ${cidrBlock}. Must be in the format x.x.x.x/y.`
+ )
+ );
+ }
+ );
+
+ it.each([
+ [IPv4Network.Any, /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
+ [IPv4Network.Loopback, /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
+ [IPv4Network.PrivateA, /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/],
+ [
+ IPv4Network.PrivateB,
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/,
+ ],
+ [IPv4Network.PrivateC, /^192\.168\.\d{1,3}\.\d{1,3}$/],
+ [IPv4Network.TestNet1, /^192\.0\.2\.\d{1,3}$/],
+ [IPv4Network.TestNet2, /^198\.51\.100\.\d{1,3}$/],
+ [IPv4Network.TestNet3, /^203\.0\.113\.\d{1,3}$/],
+ [IPv4Network.LinkLocal, /^169\.254\.\d{1,3}\.\d{1,3}$/],
+ [
+ IPv4Network.Multicast,
+ /^2(2[4-9]|3[0-9])\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,
+ ],
+ ] as const)(
+ 'should return a random IPv4 for %s network',
+ (network, regex) => {
+ const actual = faker.internet.ipv4({ network });
+
+ expect(actual).toBeTruthy();
+ expect(actual).toBeTypeOf('string');
+ expect(actual).toSatisfy((value: string) =>
+ validator.isIP(value, 4)
+ );
+ expect(actual).toMatch(regex);
+ }
+ );
});
describe('ipv6()', () => {