diff --git a/packages/better_networking/test/utils/auth/auth_handling_test.dart b/packages/better_networking/test/utils/auth/auth_handling_test.dart new file mode 100644 index 00000000..b461ea16 --- /dev/null +++ b/packages/better_networking/test/utils/auth/auth_handling_test.dart @@ -0,0 +1,338 @@ +import 'package:better_networking/better_networking.dart'; +import 'package:test/test.dart'; + +void main() { + group('Authentication Handling Tests', () { + test( + 'given sendHttpRequest when no authentication is provided then it should not throw any error', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + final result = await sendHttpRequest( + 'test-request', + APIType.rest, + null, + httpRequestModel, + ); + + expect( + result.$1?.request?.url.toString(), + equals('https://api.apidash.dev/users'), + ); + }, + ); + test( + 'given handleAuth when no authentication is provided then it should return the same httpRequestModel', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + final result = await handleAuth(httpRequestModel, null); + + expect(result.headers, isNull); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when none authentication type is provided then it should add any headers or throw errors', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const authModel = AuthModel(type: APIAuthType.none); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isEmpty); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when basic authentication fields are provided then it should add an authorization header', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const authModel = AuthModel(type: APIAuthType.basic, basic: basicAuth); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isNotEmpty); + expect( + result.headers?.any((h) => h.name.toLowerCase() == 'authorization'), + isTrue, + ); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when handle bearer authentication fields are provided then it should add an authorization header', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const bearerAuth = AuthBearerModel(token: 'bearer-token-123'); + const authModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isNotEmpty); + expect( + result.headers?.any((h) => h.name.toLowerCase() == 'authorization'), + isTrue, + ); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when API key authentication fields are provided then it should add an authorization header', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key-123', + location: 'header', + name: 'X-API-Key', + ); + const authModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isNotEmpty); + expect( + result.headers?.any((h) => h.name.toLowerCase() == 'x-api-key'), + isTrue, + ); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when API key authentication fields are provided then it should add an authorization query', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key-123', + location: 'query', + name: 'apikey', + ); + const authModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.params, isNotEmpty); + expect(result.params?.any((p) => p.name == 'apikey'), isTrue); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when JWT authentication fields are provided then it should add an authorization header', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const jwtAuth = AuthJwtModel( + secret: 'jwt-secret', + payload: '{"sub": "1234567890"}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ); + const authModel = AuthModel(type: APIAuthType.jwt, jwt: jwtAuth); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isNotEmpty); + expect( + result.headers?.any((h) => h.name.toLowerCase() == 'authorization'), + isTrue, + ); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when digest authentication fields are provided then it should add an authorization header', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const digestAuth = AuthDigestModel( + username: 'digestuser', + password: 'digestpass', + realm: 'test-realm', + nonce: 'test-nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'test-opaque', + ); + const authModel = AuthModel( + type: APIAuthType.digest, + digest: digestAuth, + ); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isNotEmpty); + expect( + result.headers?.any((h) => h.name.toLowerCase() == 'authorization'), + isTrue, + ); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when multiple headers are provided then it should add an authorization header to the existing headers', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + headers: [ + NameValueModel(name: 'Content-Type', value: 'application/json'), + NameValueModel(name: 'Accept', value: 'application/json'), + ], + ); + + const bearerAuth = AuthBearerModel(token: 'bearer-token-123'); + const authModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isNotEmpty); + expect(result.headers?.any((h) => h.name == 'Content-Type'), isTrue); + expect(result.headers?.any((h) => h.name == 'Accept'), isTrue); + expect( + result.headers?.any((h) => h.name.toLowerCase() == 'authorization'), + isTrue, + ); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when multiple params are provided then it should add it to the existing params', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + params: [ + NameValueModel(name: 'limit', value: '10'), + NameValueModel(name: 'offset', value: '0'), + ], + ); + + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key-123', + location: 'query', + name: 'apikey', + ); + const authModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.params, isNotEmpty); + expect(result.params?.any((p) => p.name == 'limit'), isTrue); + expect(result.params?.any((p) => p.name == 'offset'), isTrue); + expect(result.params?.any((p) => p.name == 'apikey'), isTrue); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when special characters are provided it should not throw an error', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const basicAuth = AuthBasicAuthModel( + username: 'user@domain.com', + password: r'P@ssw0rd!@#$%^&*()', + ); + const authModel = AuthModel(type: APIAuthType.basic, basic: basicAuth); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isNotEmpty); + expect( + result.headers?.any((h) => h.name.toLowerCase() == 'authorization'), + isTrue, + ); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + + test( + 'given handleAuth when no values are provided it should not throw an error', + () async { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + const basicAuth = AuthBasicAuthModel(username: '', password: ''); + const authModel = AuthModel(type: APIAuthType.basic, basic: basicAuth); + + final result = await handleAuth(httpRequestModel, authModel); + + expect(result.headers, isNotEmpty); + expect( + result.headers?.any((h) => h.name.toLowerCase() == 'authorization'), + isTrue, + ); + expect(result.url, equals('https://api.apidash.dev/users')); + }, + ); + }); +} diff --git a/packages/better_networking/test/utils/auth/auth_models_test.dart b/packages/better_networking/test/utils/auth/auth_models_test.dart new file mode 100644 index 00000000..1c7b0cd2 --- /dev/null +++ b/packages/better_networking/test/utils/auth/auth_models_test.dart @@ -0,0 +1,340 @@ +import 'package:better_networking/better_networking.dart'; +import 'package:test/test.dart'; + +void main() { + group('AuthModel Tests', () { + test('should create AuthModel with none type', () { + const authModel = AuthModel(type: APIAuthType.none); + + expect(authModel.type, APIAuthType.none); + expect(authModel.basic, isNull); + expect(authModel.bearer, isNull); + expect(authModel.apikey, isNull); + expect(authModel.jwt, isNull); + expect(authModel.digest, isNull); + }); + + test('should create AuthModel with basic authentication', () { + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + expect(authModel.type, APIAuthType.basic); + expect(authModel.basic, isNotNull); + expect(authModel.basic?.username, 'testuser'); + expect(authModel.basic?.password, 'testpass'); + }); + + test('should create AuthModel with bearer token', () { + const bearerAuth = AuthBearerModel(token: 'bearer-token-123'); + + const authModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + + expect(authModel.type, APIAuthType.bearer); + expect(authModel.bearer, isNotNull); + expect(authModel.bearer?.token, 'bearer-token-123'); + }); + + test('should create AuthModel with API key authentication', () { + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key-123', + location: 'header', + name: 'X-API-Key', + ); + + const authModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + + expect(authModel.type, APIAuthType.apiKey); + expect(authModel.apikey, isNotNull); + expect(authModel.apikey?.key, 'api-key-123'); + expect(authModel.apikey?.location, 'header'); + expect(authModel.apikey?.name, 'X-API-Key'); + }); + + test('should create AuthModel with JWT authentication', () { + const jwtAuth = AuthJwtModel( + secret: 'jwt-secret', + payload: '{"sub": "1234567890"}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ); + + const authModel = AuthModel( + type: APIAuthType.jwt, + jwt: jwtAuth, + ); + + expect(authModel.type, APIAuthType.jwt); + expect(authModel.jwt, isNotNull); + expect(authModel.jwt?.secret, 'jwt-secret'); + expect(authModel.jwt?.algorithm, 'HS256'); + expect(authModel.jwt?.isSecretBase64Encoded, false); + }); + + test('should create AuthModel with digest authentication', () { + const digestAuth = AuthDigestModel( + username: 'digestuser', + password: 'digestpass', + realm: 'test-realm', + nonce: 'test-nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'test-opaque', + ); + + const authModel = AuthModel( + type: APIAuthType.digest, + digest: digestAuth, + ); + + expect(authModel.type, APIAuthType.digest); + expect(authModel.digest, isNotNull); + expect(authModel.digest?.username, 'digestuser'); + expect(authModel.digest?.realm, 'test-realm'); + expect(authModel.digest?.algorithm, 'MD5'); + }); + + test('should serialize and deserialize AuthModel correctly', () { + const originalModel = AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ), + ); + + final json = originalModel.toJson(); + final deserializedModel = AuthModel.fromJson(json); + + expect(deserializedModel.type, originalModel.type); + expect(deserializedModel.basic?.username, originalModel.basic?.username); + expect(deserializedModel.basic?.password, originalModel.basic?.password); + }); + + test('should handle copyWith for AuthModel', () { + const originalModel = AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ), + ); + + const newBasicAuth = AuthBasicAuthModel( + username: 'newuser', + password: 'newpass', + ); + + final copiedModel = originalModel.copyWith( + type: APIAuthType.basic, + basic: newBasicAuth, + ); + + expect(copiedModel.type, APIAuthType.basic); + expect(copiedModel.basic?.username, 'newuser'); + expect(copiedModel.basic?.password, 'newpass'); + }); + + test('should handle API key with default values', () { + const apiKeyAuth = AuthApiKeyModel(key: 'test-key'); + + expect(apiKeyAuth.key, 'test-key'); + expect(apiKeyAuth.location, 'header'); + expect(apiKeyAuth.name, 'x-api-key'); + }); + + test('should handle API key with custom values', () { + const apiKeyAuth = AuthApiKeyModel( + key: 'custom-key', + location: 'query', + name: 'api_key', + ); + + expect(apiKeyAuth.key, 'custom-key'); + expect(apiKeyAuth.location, 'query'); + expect(apiKeyAuth.name, 'api_key'); + }); + + test('should handle JWT with private key', () { + const jwtAuth = AuthJwtModel( + secret: 'jwt-secret', + privateKey: 'private-key-content', + payload: '{"sub": "1234567890"}', + addTokenTo: 'header', + algorithm: 'RS256', + isSecretBase64Encoded: true, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ); + + expect(jwtAuth.secret, 'jwt-secret'); + expect(jwtAuth.privateKey, 'private-key-content'); + expect(jwtAuth.algorithm, 'RS256'); + expect(jwtAuth.isSecretBase64Encoded, true); + }); + + test('should handle edge cases with empty strings', () { + const basicAuth = AuthBasicAuthModel( + username: '', + password: '', + ); + + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + expect(authModel.basic?.username, ''); + expect(authModel.basic?.password, ''); + }); + + test('should handle JSON serialization with null values', () { + const authModel = AuthModel(type: APIAuthType.none); + + final json = authModel.toJson(); + final deserializedModel = AuthModel.fromJson(json); + + expect(deserializedModel.type, APIAuthType.none); + expect(deserializedModel.basic, isNull); + expect(deserializedModel.bearer, isNull); + expect(deserializedModel.apikey, isNull); + expect(deserializedModel.jwt, isNull); + expect(deserializedModel.digest, isNull); + }); + + test('should handle complex JWT payload', () { + const complexPayload = ''' + { + "sub": "1234567890", + "name": "John Doe", + "iat": 1516239022, + "exp": 1516242622, + "roles": ["admin", "user"], + "permissions": { + "read": true, + "write": false + } + } + '''; + + const jwtAuth = AuthJwtModel( + secret: 'complex-secret', + payload: complexPayload, + addTokenTo: 'header', + algorithm: 'HS512', + isSecretBase64Encoded: false, + headerPrefix: 'JWT', + queryParamKey: 'jwt_token', + header: 'X-JWT-Token', + ); + + expect(jwtAuth.payload, complexPayload); + expect(jwtAuth.headerPrefix, 'JWT'); + expect(jwtAuth.queryParamKey, 'jwt_token'); + expect(jwtAuth.header, 'X-JWT-Token'); + }); + + test('should handle digest auth with all parameters', () { + const digestAuth = AuthDigestModel( + username: 'digestuser', + password: 'digestpass', + realm: 'api.example.com', + nonce: 'dcd98b7102dd2f0e8b11d0f600bfb0c093', + algorithm: 'SHA-256', + qop: 'auth-int', + opaque: '5ccc069c403ebaf9f0171e9517f40e41', + ); + + expect(digestAuth.username, 'digestuser'); + expect(digestAuth.password, 'digestpass'); + expect(digestAuth.realm, 'api.example.com'); + expect(digestAuth.nonce, 'dcd98b7102dd2f0e8b11d0f600bfb0c093'); + expect(digestAuth.algorithm, 'SHA-256'); + expect(digestAuth.qop, 'auth-int'); + expect(digestAuth.opaque, '5ccc069c403ebaf9f0171e9517f40e41'); + }); + }); + + test('should handle type mismatch scenarios', () { + // Test when type is basic but bearer data is provided + const authModel = AuthModel( + type: APIAuthType.basic, + bearer: AuthBearerModel(token: 'token'), + ); + + expect(authModel.type, APIAuthType.basic); + expect(authModel.bearer?.token, 'token'); + expect(authModel.basic, isNull); + }); + + test('should handle multiple auth types provided', () { + const authModel = AuthModel( + type: APIAuthType.bearer, + basic: AuthBasicAuthModel(username: 'user', password: 'pass'), + bearer: AuthBearerModel(token: 'token'), + apikey: AuthApiKeyModel(key: 'key'), + ); + + expect(authModel.type, APIAuthType.bearer); + expect(authModel.basic, isNotNull); + expect(authModel.bearer, isNotNull); + expect(authModel.apikey, isNotNull); + }); + + test('should handle serialization with special characters', () { + const basicAuth = AuthBasicAuthModel( + username: 'user@domain.com', + password: r'P@ssw0rd!@#$%^&*()', + ); + + const authModel = AuthModel(type: APIAuthType.basic, basic: basicAuth); + + final json = authModel.toJson(); + final deserializedModel = AuthModel.fromJson(json); + + expect(deserializedModel.basic?.username, 'user@domain.com'); + expect(deserializedModel.basic?.password, r'P@ssw0rd!@#$%^&*()'); + }); + + test('should handle very long strings', () { + final longString = 'a' * 1000; + + final bearerAuth = AuthBearerModel(token: longString); + final authModel = AuthModel(type: APIAuthType.bearer, bearer: bearerAuth); + + expect(authModel.bearer?.token, longString); + expect(authModel.bearer?.token.length, 1000); + }); + + test('should handle Unicode characters', () { + const basicAuth = AuthBasicAuthModel( + username: 'user_测试_тест_テスト', + password: 'password_🔑_🚀_💻', + ); + + const authModel = AuthModel(type: APIAuthType.basic, basic: basicAuth); + + final json = authModel.toJson(); + final deserializedModel = AuthModel.fromJson(json); + + expect(deserializedModel.basic?.username, 'user_测试_тест_テスト'); + expect(deserializedModel.basic?.password, 'password_🔑_🚀_💻'); + }); +} diff --git a/pubspec.lock b/pubspec.lock index d73a53ca..97b7d5b2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "82.0.0" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" analyzer: dependency: transitive description: @@ -349,6 +357,14 @@ packages: relative: true source: path version: "0.1.3" + dart_jsonwebtoken: + dependency: transitive + description: + name: dart_jsonwebtoken + sha256: "21ce9f8a8712f741e8d6876a9c82c0f8a257fe928c4378a91d8527b92a3fd413" + url: "https://pub.dev" + source: hosted + version: "3.2.0" dart_style: dependency: "direct main" description: @@ -381,6 +397,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + ed25519_edwards: + dependency: transitive + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" equatable: dependency: transitive description: @@ -1246,6 +1270,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" + url: "https://pub.dev" + source: hosted + version: "4.0.0" pool: dependency: transitive description: diff --git a/test/models/history_models.dart b/test/models/history_models.dart index 557b3a2e..6de41dc1 100644 --- a/test/models/history_models.dart +++ b/test/models/history_models.dart @@ -18,11 +18,11 @@ final historyMetaModel1 = HistoryMetaModel( /// Basic History Request model 1 final historyRequestModel1 = HistoryRequestModel( - historyId: 'historyId1', - metaData: historyMetaModel1, - httpRequestModel: httpRequestModelGet4, - httpResponseModel: responseModel, -); + historyId: 'historyId1', + metaData: historyMetaModel1, + httpRequestModel: httpRequestModelGet4, + httpResponseModel: responseModel, + authModel: AuthModel(type: APIAuthType.none)); final historyMetaModel2 = HistoryMetaModel( historyId: 'historyId2', @@ -35,11 +35,11 @@ final historyMetaModel2 = HistoryMetaModel( ); final historyRequestModel2 = HistoryRequestModel( - historyId: 'historyId2', - metaData: historyMetaModel2, - httpRequestModel: httpRequestModelPost10, - httpResponseModel: responseModel, -); + historyId: 'historyId2', + metaData: historyMetaModel2, + httpRequestModel: httpRequestModelPost10, + httpResponseModel: responseModel, + authModel: AuthModel(type: APIAuthType.none)); /// JSONs final Map historyMetaModelJson1 = { @@ -59,7 +59,15 @@ final Map historyRequestModelJson1 = { "httpRequestModel": httpRequestModelGet4Json, "httpResponseModel": responseModelJson, 'preRequestScript': null, - 'postRequestScript': null + 'postRequestScript': null, + 'authModel': { + 'type': 'none', + 'apikey': null, + 'bearer': null, + 'basic': null, + 'jwt': null, + 'digest': null + } }; final Map historyMetaModelJson2 = { diff --git a/test/models/http_request_model_test.dart b/test/models/http_request_model_test.dart index 78a52097..2a5cd397 100644 --- a/test/models/http_request_model_test.dart +++ b/test/models/http_request_model_test.dart @@ -83,4 +83,325 @@ void main() { var httpRequestModel3 = httpRequestModel.copyWith(headers: null); expect(httpRequestModel3.headers, null); }); + + group('HttpRequestModel Auth Tests', () { + test('should create HttpRequestModel with no authentication', () { + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + ); + + expect(httpRequestModel.authModel?.type, APIAuthType.none); + }); + + test('should create HttpRequestModel with basic authentication', () { + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + authModel: authModel, + ); + + expect(httpRequestModel.authModel, isNotNull); + expect(httpRequestModel.authModel?.type, APIAuthType.basic); + expect(httpRequestModel.authModel?.basic?.username, 'testuser'); + expect(httpRequestModel.authModel?.basic?.password, 'testpass'); + }); + + test('should create HttpRequestModel with bearer authentication', () { + const bearerAuth = AuthBearerModel(token: 'bearer-token-123'); + const authModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.post, + url: 'https://api.apidash.dev/users', + authModel: authModel, + ); + + expect(httpRequestModel.authModel?.type, APIAuthType.bearer); + expect(httpRequestModel.authModel?.bearer?.token, 'bearer-token-123'); + }); + + test('should create HttpRequestModel with API key authentication', () { + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key-123', + location: 'header', + name: 'X-API-Key', + ); + const authModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + authModel: authModel, + ); + + expect(httpRequestModel.authModel?.type, APIAuthType.apiKey); + expect(httpRequestModel.authModel?.apikey?.key, 'api-key-123'); + expect(httpRequestModel.authModel?.apikey?.location, 'header'); + expect(httpRequestModel.authModel?.apikey?.name, 'X-API-Key'); + }); + + test('should create HttpRequestModel with JWT authentication', () { + const jwtAuth = AuthJwtModel( + secret: 'jwt-secret', + payload: '{"sub": "1234567890"}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ); + const authModel = AuthModel( + type: APIAuthType.jwt, + jwt: jwtAuth, + ); + + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.patch, + url: 'https://api.apidash.dev/users/1', + authModel: authModel, + ); + + expect(httpRequestModel.authModel?.type, APIAuthType.jwt); + expect(httpRequestModel.authModel?.jwt?.secret, 'jwt-secret'); + expect(httpRequestModel.authModel?.jwt?.algorithm, 'HS256'); + expect(httpRequestModel.authModel?.jwt?.isSecretBase64Encoded, false); + }); + + test('should create HttpRequestModel with digest authentication', () { + const digestAuth = AuthDigestModel( + username: 'digestuser', + password: 'digestpass', + realm: 'test-realm', + nonce: 'test-nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'test-opaque', + ); + const authModel = AuthModel( + type: APIAuthType.digest, + digest: digestAuth, + ); + + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.delete, + url: 'https://api.apidash.dev/users/1', + authModel: authModel, + ); + + expect(httpRequestModel.authModel?.type, APIAuthType.digest); + expect(httpRequestModel.authModel?.digest?.username, 'digestuser'); + expect(httpRequestModel.authModel?.digest?.realm, 'test-realm'); + expect(httpRequestModel.authModel?.digest?.algorithm, 'MD5'); + }); + + test( + 'should serialize and deserialize HttpRequestModel with auth correctly', + () { + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + const originalModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + authModel: authModel, + ); + + final json = originalModel.toJson(); + final deserializedModel = HttpRequestModel.fromJson(json); + + expect(deserializedModel.method, originalModel.method); + expect(deserializedModel.url, originalModel.url); + expect(deserializedModel.authModel?.type, originalModel.authModel?.type); + expect(deserializedModel.authModel?.basic?.username, + originalModel.authModel?.basic?.username); + expect(deserializedModel.authModel?.basic?.password, + originalModel.authModel?.basic?.password); + }); + + test('should handle copyWith for HttpRequestModel with auth', () { + const originalAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const originalAuthModel = AuthModel( + type: APIAuthType.basic, + basic: originalAuth, + ); + + const originalModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + authModel: originalAuthModel, + ); + + const newAuth = AuthBearerModel(token: 'new-bearer-token'); + const newAuthModel = AuthModel( + type: APIAuthType.bearer, + bearer: newAuth, + ); + + final copiedModel = originalModel.copyWith( + authModel: newAuthModel, + ); + + expect(copiedModel.method, originalModel.method); + expect(copiedModel.url, originalModel.url); + expect(copiedModel.authModel?.type, APIAuthType.bearer); + expect(copiedModel.authModel?.bearer?.token, 'new-bearer-token'); + }); + + test('should handle HttpRequestModel with complex auth scenarios', () { + const complexPayload = ''' + { + "sub": "1234567890", + "name": "John Doe", + "iat": 1516239022, + "exp": 1516242622, + "roles": ["admin", "user"], + "permissions": { + "read": true, + "write": false + } + } + '''; + + const jwtAuth = AuthJwtModel( + secret: 'complex-secret', + privateKey: 'private-key-content', + payload: complexPayload, + addTokenTo: 'query', + algorithm: 'RS256', + isSecretBase64Encoded: true, + headerPrefix: 'JWT', + queryParamKey: 'jwt_token', + header: 'X-JWT-Token', + ); + const authModel = AuthModel( + type: APIAuthType.jwt, + jwt: jwtAuth, + ); + + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.post, + url: 'https://api.apidash.dev/secure-endpoint', + authModel: authModel, + headers: [ + NameValueModel(name: 'Content-Type', value: 'application/json'), + NameValueModel(name: 'Accept', value: 'application/json'), + ], + ); + + expect(httpRequestModel.authModel?.jwt?.payload, complexPayload); + expect( + httpRequestModel.authModel?.jwt?.privateKey, 'private-key-content'); + expect(httpRequestModel.authModel?.jwt?.algorithm, 'RS256'); + expect(httpRequestModel.authModel?.jwt?.isSecretBase64Encoded, true); + expect(httpRequestModel.authModel?.jwt?.addTokenTo, 'query'); + }); + + test('should handle HttpRequestModel with auth and other fields', () { + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key-123', + location: 'header', + name: 'X-API-Key', + ); + const authModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + + const httpRequestModel = HttpRequestModel( + method: HTTPVerb.post, + url: 'https://api.apidash.dev/users', + authModel: authModel, + headers: [ + NameValueModel(name: 'Content-Type', value: 'application/json'), + NameValueModel(name: 'Accept', value: 'application/json'), + ], + params: [ + NameValueModel(name: 'limit', value: '10'), + NameValueModel(name: 'offset', value: '0'), + ], + body: '{"name": "John Doe", "email": "john@example.com"}', + bodyContentType: ContentType.json, + ); + + expect(httpRequestModel.authModel?.type, APIAuthType.apiKey); + expect(httpRequestModel.authModel?.apikey?.key, 'api-key-123'); + expect(httpRequestModel.headers?.length, 2); + expect(httpRequestModel.params?.length, 2); + expect(httpRequestModel.body, + '{"name": "John Doe", "email": "john@example.com"}'); + expect(httpRequestModel.bodyContentType, ContentType.json); + }); + + test('should handle HttpRequestModel with multiple auth types in sequence', + () { + const originalAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const originalAuthModel = AuthModel( + type: APIAuthType.basic, + basic: originalAuth, + ); + + var httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.apidash.dev/users', + authModel: originalAuthModel, + ); + + expect(httpRequestModel.authModel?.type, APIAuthType.basic); + + // Change to bearer + const bearerAuth = AuthBearerModel(token: 'bearer-token'); + const bearerAuthModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + + httpRequestModel = httpRequestModel.copyWith(authModel: bearerAuthModel); + expect(httpRequestModel.authModel?.type, APIAuthType.bearer); + + // Change to API key + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key', + location: 'query', + name: 'key', + ); + const apiKeyAuthModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + + httpRequestModel = httpRequestModel.copyWith(authModel: apiKeyAuthModel); + expect(httpRequestModel.authModel?.type, APIAuthType.apiKey); + expect(httpRequestModel.authModel?.apikey?.location, 'query'); + expect(httpRequestModel.authModel?.apikey?.name, 'key'); + }); + }); } diff --git a/test/models/http_request_models.dart b/test/models/http_request_models.dart index 10b84eda..f5980fbe 100644 --- a/test/models/http_request_models.dart +++ b/test/models/http_request_models.dart @@ -384,6 +384,14 @@ const httpRequestModelGet4Json = { {'name': 'add_space', 'value': 'true'}, {'name': 'trailing_zeros', 'value': 'true'} ], + 'authModel': { + 'type': 'none', + 'apikey': null, + 'bearer': null, + 'basic': null, + 'jwt': null, + 'digest': null + }, "isHeaderEnabledList": null, "isParamEnabledList": null, "bodyContentType": "json", @@ -403,6 +411,14 @@ const httpRequestModelPost10Json = { {'name': 'size', 'value': '2'}, {'name': 'len', 'value': '3'} ], + 'authModel': { + 'type': 'none', + 'apikey': null, + 'bearer': null, + 'basic': null, + 'jwt': null, + 'digest': null + }, 'isHeaderEnabledList': [false, true], 'isParamEnabledList': null, "bodyContentType": 'json', diff --git a/test/models/request_models.dart b/test/models/request_models.dart index 9c89bc19..1762706c 100644 --- a/test/models/request_models.dart +++ b/test/models/request_models.dart @@ -7,7 +7,6 @@ import 'http_response_models.dart'; const requestModelGet1 = RequestModel( id: 'get1', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet1, ); @@ -15,7 +14,6 @@ const requestModelGet1 = RequestModel( const requestModelGet2 = RequestModel( id: 'get2', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet2, ); @@ -23,7 +21,6 @@ const requestModelGet2 = RequestModel( const requestModelGet3 = RequestModel( id: 'get3', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet3, ); @@ -31,7 +28,6 @@ const requestModelGet3 = RequestModel( const requestModelGet4 = RequestModel( id: 'get4', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet4, ); @@ -39,7 +35,6 @@ const requestModelGet4 = RequestModel( const requestModelGet5 = RequestModel( id: 'get5', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet5, ); @@ -47,7 +42,6 @@ const requestModelGet5 = RequestModel( const requestModelGet6 = RequestModel( id: 'get6', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet6, ); @@ -55,7 +49,6 @@ const requestModelGet6 = RequestModel( const requestModelGet7 = RequestModel( id: 'get7', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet7, ); @@ -63,7 +56,6 @@ const requestModelGet7 = RequestModel( const requestModelGet8 = RequestModel( id: 'get8', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet8, ); @@ -71,7 +63,6 @@ const requestModelGet8 = RequestModel( const requestModelGet9 = RequestModel( id: 'get9', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet9, ); @@ -79,7 +70,6 @@ const requestModelGet9 = RequestModel( const requestModelGet10 = RequestModel( id: 'get10', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet10, ); @@ -87,7 +77,6 @@ const requestModelGet10 = RequestModel( const requestModelGet11 = RequestModel( id: 'get11', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet11, ); @@ -95,7 +84,6 @@ const requestModelGet11 = RequestModel( const requestModelGet12 = RequestModel( id: 'get12', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet12, ); @@ -103,7 +91,6 @@ const requestModelGet12 = RequestModel( const requestModelHead1 = RequestModel( id: 'head1', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelHead1, ); @@ -111,7 +98,6 @@ const requestModelHead1 = RequestModel( const requestModelHead2 = RequestModel( id: 'head2', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelHead2, ); @@ -119,7 +105,6 @@ const requestModelHead2 = RequestModel( const requestModelPost1 = RequestModel( id: 'post1', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost1, ); @@ -127,7 +112,6 @@ const requestModelPost1 = RequestModel( const requestModelPost2 = RequestModel( id: 'post2', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost2, ); @@ -135,7 +119,6 @@ const requestModelPost2 = RequestModel( const requestModelPost3 = RequestModel( id: 'post3', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost3, ); @@ -143,7 +126,6 @@ const requestModelPost3 = RequestModel( const requestModelPost4 = RequestModel( id: 'post4', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost4, ); @@ -151,7 +133,6 @@ const requestModelPost4 = RequestModel( const requestModelPost5 = RequestModel( id: 'post5', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost5, ); @@ -159,7 +140,6 @@ const requestModelPost5 = RequestModel( const requestModelPost6 = RequestModel( id: 'post6', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost6, ); @@ -167,7 +147,6 @@ const requestModelPost6 = RequestModel( const requestModelPost7 = RequestModel( id: 'post7', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost7, ); @@ -175,7 +154,6 @@ const requestModelPost7 = RequestModel( const requestModelPost8 = RequestModel( id: 'post8', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost8, ); @@ -183,14 +161,12 @@ const requestModelPost8 = RequestModel( const requestModelPost9 = RequestModel( id: 'post9', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost9, ); const requestModelPost10 = RequestModel( id: 'post9', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost10, ); @@ -198,7 +174,6 @@ const requestModelPost10 = RequestModel( const requestModelPut1 = RequestModel( id: 'put1', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPut1, ); @@ -206,7 +181,6 @@ const requestModelPut1 = RequestModel( const requestModelPatch1 = RequestModel( id: 'patch1', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPatch1, ); @@ -214,7 +188,6 @@ const requestModelPatch1 = RequestModel( const requestModelDelete1 = RequestModel( id: 'delete1', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelDelete1, ); @@ -222,7 +195,6 @@ const requestModelDelete1 = RequestModel( const requestModelDelete2 = RequestModel( id: 'delete2', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelDelete2, ); @@ -230,7 +202,6 @@ const requestModelDelete2 = RequestModel( RequestModel testRequestModel = RequestModel( id: '1', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost10, responseStatus: 200, httpResponseModel: responseModel, @@ -241,7 +212,6 @@ Map requestModelJson = { 'id': '1', 'apiType': 'rest', 'name': '', - 'authModel': '', 'description': '', 'httpRequestModel': httpRequestModelPost10Json, 'responseStatus': 200, @@ -255,7 +225,6 @@ Map requestModelJson = { const requestModelGet13 = RequestModel( id: 'get13', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGet13, ); @@ -263,7 +232,6 @@ const requestModelGet13 = RequestModel( const requestModelGetBadSSL = RequestModel( id: 'badSSL', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelGetBadSSL, ); @@ -271,7 +239,6 @@ const requestModelGetBadSSL = RequestModel( const requestModelPost11 = RequestModel( id: 'post11', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost11, ); @@ -279,7 +246,6 @@ const requestModelPost11 = RequestModel( const requestModelPost12 = RequestModel( id: 'post12', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost12, ); @@ -287,13 +253,11 @@ const requestModelPost12 = RequestModel( const requestModelPost13 = RequestModel( id: 'post13', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelPost13, ); const requestModelOptions1 = RequestModel( id: 'options1', apiType: APIType.rest, - authModel: AuthModel(type: APIAuthType.none), httpRequestModel: httpRequestModelOptions1, ); diff --git a/test/models/response_model_test.dart b/test/models/response_model_test.dart index bef5eff8..56ba1e2a 100644 --- a/test/models/response_model_test.dart +++ b/test/models/response_model_test.dart @@ -17,7 +17,7 @@ void main() { var responseRec = await sendHttpRequest( requestModelGet1.id, requestModelGet1.apiType, - requestModelGet1.authModel, + AuthModel(type: APIAuthType.none), requestModelGet1.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: false, @@ -36,7 +36,7 @@ void main() { var responseRec = await sendHttpRequest( requestModelGet13.id, requestModelGet13.apiType, - requestModelGet13.authModel, + AuthModel(type: APIAuthType.none), requestModelGet13.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: false, @@ -54,7 +54,7 @@ void main() { var responseRec = await sendHttpRequest( requestModelPost11.id, requestModelPost11.apiType, - requestModelPost11.authModel, + AuthModel(type: APIAuthType.none), requestModelPost11.httpRequestModel!, ); @@ -69,7 +69,7 @@ void main() { var responseRec = await sendHttpRequest( requestModelPost12.id, requestModelPost12.apiType, - requestModelPost12.authModel, + AuthModel(type: APIAuthType.none), requestModelPost12.httpRequestModel!, ); @@ -83,7 +83,7 @@ void main() { var responseRec = await sendHttpRequest( requestModelPost13.id, requestModelPost13.apiType, - requestModelPost13.authModel, + AuthModel(type: APIAuthType.none), requestModelPost13.httpRequestModel!, ); @@ -97,7 +97,7 @@ void main() { var responseRec = await sendHttpRequest( requestModelGetBadSSL.id, requestModelGetBadSSL.apiType, - requestModelGetBadSSL.authModel, + AuthModel(type: APIAuthType.none), requestModelGetBadSSL.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: false, @@ -110,7 +110,7 @@ void main() { var responseRec = await sendHttpRequest( requestModelGetBadSSL.id, requestModelGetBadSSL.apiType, - requestModelGetBadSSL.authModel, + AuthModel(type: APIAuthType.none), requestModelGetBadSSL.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: true, @@ -131,7 +131,7 @@ void main() { var responseRec = await sendHttpRequest( requestModelOptions1.id, requestModelOptions1.apiType, - requestModelOptions1.authModel, + AuthModel(type: APIAuthType.none), requestModelOptions1.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: false, @@ -139,9 +139,14 @@ void main() { final responseData = responseModel.fromResponse(response: responseRec.$1!); expect(responseData.statusCode, 200); - expect(responseData.headers?['access-control-allow-methods'], 'GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS'); - expect(responseData.headers?['access-control-allow-methods']?.contains("OPTIONS"), true); - expect(responseData.headers?['allow'], 'GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS'); + expect(responseData.headers?['access-control-allow-methods'], + 'GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS'); + expect( + responseData.headers?['access-control-allow-methods'] + ?.contains("OPTIONS"), + true); + expect(responseData.headers?['allow'], + 'GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS'); expect(responseData.headers?['allow']?.contains("OPTIONS"), true); }); } diff --git a/test/providers/collection_providers_test.dart b/test/providers/collection_providers_test.dart index 097b71f0..3bf59f83 100644 --- a/test/providers/collection_providers_test.dart +++ b/test/providers/collection_providers_test.dart @@ -51,4 +51,580 @@ void main() async { // Verify that the Snackbar is shown expect(find.text('Switched to POST method'), findsOneWidget); }, skip: true); + + group('CollectionStateNotifier Auth Tests', () { + late ProviderContainer container; + late CollectionStateNotifier notifier; + + setUp(() { + container = createContainer(); + notifier = container.read(collectionStateNotifierProvider.notifier); + }); + + test('should update request with basic authentication', () { + final id = notifier.state!.entries.first.key; + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect( + updatedRequest?.httpRequestModel?.authModel?.type, APIAuthType.basic); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.username, + 'testuser'); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.password, + 'testpass'); + }); + + test('should update request with bearer authentication', () { + final id = notifier.state!.entries.first.key; + const bearerAuth = AuthBearerModel(token: 'bearer-token-123'); + const authModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.type, + APIAuthType.bearer); + expect(updatedRequest?.httpRequestModel?.authModel?.bearer?.token, + 'bearer-token-123'); + }); + + test('should update request with API key authentication', () { + final id = notifier.state!.entries.first.key; + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key-123', + location: 'header', + name: 'X-API-Key', + ); + const authModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.type, + APIAuthType.apiKey); + expect(updatedRequest?.httpRequestModel?.authModel?.apikey?.key, + 'api-key-123'); + expect(updatedRequest?.httpRequestModel?.authModel?.apikey?.location, + 'header'); + expect(updatedRequest?.httpRequestModel?.authModel?.apikey?.name, + 'X-API-Key'); + }); + + test('should update request with JWT authentication', () { + final id = notifier.state!.entries.first.key; + const jwtAuth = AuthJwtModel( + secret: 'jwt-secret', + payload: '{"sub": "1234567890"}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ); + const authModel = AuthModel( + type: APIAuthType.jwt, + jwt: jwtAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect( + updatedRequest?.httpRequestModel?.authModel?.type, APIAuthType.jwt); + expect(updatedRequest?.httpRequestModel?.authModel?.jwt?.secret, + 'jwt-secret'); + expect( + updatedRequest?.httpRequestModel?.authModel?.jwt?.algorithm, 'HS256'); + expect( + updatedRequest + ?.httpRequestModel?.authModel?.jwt?.isSecretBase64Encoded, + false); + }); + + test('should update request with digest authentication', () { + final id = notifier.state!.entries.first.key; + const digestAuth = AuthDigestModel( + username: 'digestuser', + password: 'digestpass', + realm: 'test-realm', + nonce: 'test-nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'test-opaque', + ); + const authModel = AuthModel( + type: APIAuthType.digest, + digest: digestAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.type, + APIAuthType.digest); + expect(updatedRequest?.httpRequestModel?.authModel?.digest?.username, + 'digestuser'); + expect(updatedRequest?.httpRequestModel?.authModel?.digest?.realm, + 'test-realm'); + expect(updatedRequest?.httpRequestModel?.authModel?.digest?.algorithm, + 'MD5'); + }); + + test('should remove authentication when set to none', () { + final id = notifier.state!.entries.first.key; + + // First add auth + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + notifier.update(id: id, authModel: authModel); + + // Then remove auth + const noAuthModel = AuthModel(type: APIAuthType.none); + notifier.update(id: id, authModel: noAuthModel); + + final updatedRequest = notifier.getRequestModel(id); + expect( + updatedRequest?.httpRequestModel?.authModel?.type, APIAuthType.none); + expect(updatedRequest?.httpRequestModel?.authModel?.basic, isNull); + }); + + test('should preserve auth when duplicating request', () { + final id = notifier.state!.entries.first.key; + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + notifier.update(id: id, authModel: authModel); + notifier.duplicate(id: id); + + final sequence = container.read(requestSequenceProvider); + final duplicatedId = sequence.firstWhere((element) => element != id); + final duplicatedRequest = notifier.getRequestModel(duplicatedId); + + expect(duplicatedRequest?.httpRequestModel?.authModel?.type, + APIAuthType.basic); + expect(duplicatedRequest?.httpRequestModel?.authModel?.basic?.username, + 'testuser'); + expect(duplicatedRequest?.httpRequestModel?.authModel?.basic?.password, + 'testpass'); + }); + + test('should not clear auth when clearing response', () { + final id = notifier.state!.entries.first.key; + const bearerAuth = AuthBearerModel(token: 'bearer-token-123'); + const authModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + + notifier.update(id: id, authModel: authModel); + notifier.clearResponse(id: id); + + final updatedRequest = notifier.getRequestModel(id); + // Auth should be preserved when clearing response + expect(updatedRequest?.httpRequestModel?.authModel?.type, + APIAuthType.bearer); + expect(updatedRequest?.httpRequestModel?.authModel?.bearer?.token, + 'bearer-token-123'); + }); + + test('should handle auth with special characters', () { + final id = notifier.state!.entries.first.key; + const basicAuth = AuthBasicAuthModel( + username: 'user@domain.com', + password: r'P@ssw0rd!@#$%^&*()', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.username, + 'user@domain.com'); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.password, + r'P@ssw0rd!@#$%^&*()'); + }); + + test('should handle multiple auth type changes', () { + final id = notifier.state!.entries.first.key; + + // Start with basic auth + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const basicAuthModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + notifier.update(id: id, authModel: basicAuthModel); + + // Switch to bearer + const bearerAuth = AuthBearerModel(token: 'bearer-token-123'); + const bearerAuthModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + notifier.update(id: id, authModel: bearerAuthModel); + + // Switch to API key + const apiKeyAuth = AuthApiKeyModel( + key: 'api-key-123', + location: 'query', + name: 'apikey', + ); + const apiKeyAuthModel = AuthModel( + type: APIAuthType.apiKey, + apikey: apiKeyAuth, + ); + notifier.update(id: id, authModel: apiKeyAuthModel); + + final updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.type, + APIAuthType.apiKey); + expect(updatedRequest?.httpRequestModel?.authModel?.apikey?.key, + 'api-key-123'); + expect(updatedRequest?.httpRequestModel?.authModel?.apikey?.location, + 'query'); + expect( + updatedRequest?.httpRequestModel?.authModel?.apikey?.name, 'apikey'); + }); + + test('should handle empty auth values', () { + final id = notifier.state!.entries.first.key; + const basicAuth = AuthBasicAuthModel( + username: '', + password: '', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect( + updatedRequest?.httpRequestModel?.authModel?.type, APIAuthType.basic); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.username, ''); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.password, ''); + }); + + test('should save and load auth data correctly', () async { + final notifier = container.read(collectionStateNotifierProvider.notifier); + + final id = notifier.state!.entries.first.key; + const jwtAuth = AuthJwtModel( + secret: 'jwt-secret', + payload: '{"sub": "1234567890"}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ); + const authModel = AuthModel( + type: APIAuthType.jwt, + jwt: jwtAuth, + ); + + notifier.update(id: id, authModel: authModel); + await notifier.saveData(); + + // Create new container and load data + late ProviderContainer newContainer; + try { + newContainer = ProviderContainer(); + + // Wait for the container to initialize by accessing the provider + final newNotifier = + newContainer.read(collectionStateNotifierProvider.notifier); + + // Give some time for the microtask in the constructor to complete + await Future.delayed(const Duration(milliseconds: 10)); + + final loadedRequest = newNotifier.getRequestModel(id); + + expect( + loadedRequest?.httpRequestModel?.authModel?.type, APIAuthType.jwt); + expect(loadedRequest?.httpRequestModel?.authModel?.jwt?.secret, + 'jwt-secret'); + expect(loadedRequest?.httpRequestModel?.authModel?.jwt?.algorithm, + 'HS256'); + } finally { + newContainer.dispose(); + } + }); + + test('should handle auth in addRequestModel', () { + const basicAuth = AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + final httpRequestModel = HttpRequestModel( + method: HTTPVerb.get, + url: 'https://api.example.com/users', + authModel: authModel, + ); + + notifier.addRequestModel(httpRequestModel, name: 'Test Request'); + + final sequence = container.read(requestSequenceProvider); + final addedRequest = notifier.getRequestModel(sequence.first); + + expect( + addedRequest?.httpRequestModel?.authModel?.type, APIAuthType.basic); + expect(addedRequest?.httpRequestModel?.authModel?.basic?.username, + 'testuser'); + expect(addedRequest?.httpRequestModel?.authModel?.basic?.password, + 'testpass'); + }); + + test('should handle complex JWT configuration', () { + final id = notifier.state!.entries.first.key; + const complexPayload = ''' + { + "sub": "1234567890", + "name": "John Doe", + "iat": 1516239022, + "exp": 1516242622, + "roles": ["admin", "user"], + "permissions": { + "read": true, + "write": false + } + } + '''; + + const jwtAuth = AuthJwtModel( + secret: 'complex-secret', + privateKey: 'private-key-content', + payload: complexPayload, + addTokenTo: 'query', + algorithm: 'RS256', + isSecretBase64Encoded: true, + headerPrefix: 'JWT', + queryParamKey: 'jwt_token', + header: 'X-JWT-Token', + ); + const authModel = AuthModel( + type: APIAuthType.jwt, + jwt: jwtAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect( + updatedRequest?.httpRequestModel?.authModel?.type, APIAuthType.jwt); + expect(updatedRequest?.httpRequestModel?.authModel?.jwt?.payload, + complexPayload); + expect(updatedRequest?.httpRequestModel?.authModel?.jwt?.privateKey, + 'private-key-content'); + expect( + updatedRequest?.httpRequestModel?.authModel?.jwt?.algorithm, 'RS256'); + expect( + updatedRequest + ?.httpRequestModel?.authModel?.jwt?.isSecretBase64Encoded, + true); + expect(updatedRequest?.httpRequestModel?.authModel?.jwt?.addTokenTo, + 'query'); + }); + + test('should handle API key in different locations', () { + final id = notifier.state!.entries.first.key; + + // Test header location + const headerApiKey = AuthApiKeyModel( + key: 'header-key', + location: 'header', + name: 'X-API-Key', + ); + const headerAuthModel = AuthModel( + type: APIAuthType.apiKey, + apikey: headerApiKey, + ); + notifier.update(id: id, authModel: headerAuthModel); + + var updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.apikey?.location, + 'header'); + expect(updatedRequest?.httpRequestModel?.authModel?.apikey?.name, + 'X-API-Key'); + + // Test query location + const queryApiKey = AuthApiKeyModel( + key: 'query-key', + location: 'query', + name: 'apikey', + ); + const queryAuthModel = AuthModel( + type: APIAuthType.apiKey, + apikey: queryApiKey, + ); + notifier.update(id: id, authModel: queryAuthModel); + + updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.apikey?.location, + 'query'); + expect( + updatedRequest?.httpRequestModel?.authModel?.apikey?.name, 'apikey'); + }); + + test('should handle digest auth with different algorithms', () { + final id = notifier.state!.entries.first.key; + + // Test MD5 algorithm + const md5DigestAuth = AuthDigestModel( + username: 'digestuser', + password: 'digestpass', + realm: 'test-realm', + nonce: 'test-nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'test-opaque', + ); + const md5AuthModel = AuthModel( + type: APIAuthType.digest, + digest: md5DigestAuth, + ); + notifier.update(id: id, authModel: md5AuthModel); + + var updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.digest?.algorithm, + 'MD5'); + + // Test SHA-256 algorithm + const sha256DigestAuth = AuthDigestModel( + username: 'digestuser', + password: 'digestpass', + realm: 'test-realm', + nonce: 'test-nonce', + algorithm: 'SHA-256', + qop: 'auth-int', + opaque: 'test-opaque', + ); + const sha256AuthModel = AuthModel( + type: APIAuthType.digest, + digest: sha256DigestAuth, + ); + notifier.update(id: id, authModel: sha256AuthModel); + + updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.digest?.algorithm, + 'SHA-256'); + expect( + updatedRequest?.httpRequestModel?.authModel?.digest?.qop, 'auth-int'); + }); + + test('should handle auth model copyWith functionality', () { + final id = notifier.state!.entries.first.key; + const originalAuth = AuthBasicAuthModel( + username: 'original', + password: 'original', + ); + const originalAuthModel = AuthModel( + type: APIAuthType.basic, + basic: originalAuth, + ); + + notifier.update(id: id, authModel: originalAuthModel); + + // Update with copyWith + const updatedAuth = AuthBasicAuthModel( + username: 'updated', + password: 'updated', + ); + final updatedAuthModel = originalAuthModel.copyWith( + basic: updatedAuth, + ); + + notifier.update(id: id, authModel: updatedAuthModel); + + final updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.username, + 'updated'); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.password, + 'updated'); + }); + + test('should handle auth with very long tokens', () { + final id = notifier.state!.entries.first.key; + final longToken = 'a' * 5000; // Very long token + + final bearerAuth = AuthBearerModel(token: longToken); + final authModel = AuthModel( + type: APIAuthType.bearer, + bearer: bearerAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.bearer?.token, + longToken); + expect(updatedRequest?.httpRequestModel?.authModel?.bearer?.token.length, + 5000); + }); + + test('should handle auth with Unicode characters', () { + final id = notifier.state!.entries.first.key; + const basicAuth = AuthBasicAuthModel( + username: 'user_测试_тест_テスト', + password: 'password_🔑_🚀_💻', + ); + const authModel = AuthModel( + type: APIAuthType.basic, + basic: basicAuth, + ); + + notifier.update(id: id, authModel: authModel); + + final updatedRequest = notifier.getRequestModel(id); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.username, + 'user_测试_тест_テスト'); + expect(updatedRequest?.httpRequestModel?.authModel?.basic?.password, + 'password_🔑_🚀_💻'); + }); + + tearDown(() { + container.dispose(); + }); + }); }