Files
apidash/test/providers/collection_providers_test.dart
2025-08-05 06:18:21 +05:30

1234 lines
42 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/request_body.dart';
import 'package:apidash/widgets/editor.dart';
import 'package:apidash/widgets/response_body.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'helpers.dart';
void main() async {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() async {
await testSetUpTempDirForHive();
});
testWidgets(
'Request method changes from GET to POST when body is added and Snackbar is shown',
(WidgetTester tester) async {
// Set up the test environment
final container = createContainer();
final notifier = container.read(collectionStateNotifierProvider.notifier);
// Ensure the initial request is a GET request with no body
final id = notifier.state!.entries.first.key;
expect(
notifier.getRequestModel(id)!.httpRequestModel!.method, HTTPVerb.get);
expect(notifier.getRequestModel(id)!.httpRequestModel!.body, isNull);
// Build the EditRequestBody widget
await tester.pumpWidget(
ProviderScope(
// ignore: deprecated_member_use
parent: container,
child: const MaterialApp(
home: Scaffold(
body: EditRequestBody(),
),
),
),
);
// Add a body to the request, which should trigger the method change
await tester.enterText(find.byType(TextFieldEditor), 'new body added');
await tester.pump(); // Process the state change
// Verify that the request method changed to POST
expect(
notifier.getRequestModel(id)!.httpRequestModel!.method, HTTPVerb.post);
// Verify that the Snackbar is shown
expect(find.text('Switched to POST method'), findsOneWidget);
}, skip: true);
testWidgets('SSE Output is rendered correctly in UI',
(WidgetTester tester) async {
HttpOverrides.global = null; //enable networking in flutter_test
final container = createContainer();
final notifier = container.read(collectionStateNotifierProvider.notifier);
const model = HttpRequestModel(
url: 'https://sse-demo.netlify.app/sse',
method: HTTPVerb.get,
);
notifier.addRequestModel(model, name: 'sseM');
final id = notifier.state!.entries.last.key;
//runAsync to enable user-code awaiting
await tester.runAsync(() async {
await notifier.sendRequest();
await Future.delayed(const Duration(seconds: 3));
});
final rm = notifier.getRequestModel(id)!;
cancelHttpRequest(rm.id);
final sseOutput = (rm.httpResponseModel?.sseOutput ?? [])
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
expect(sseOutput, isNotEmpty, reason: 'No SSE Output found');
// Render the widget
await tester.pumpWidget(
ProviderScope(
// ignore: deprecated_member_use
parent: container,
child: MaterialApp(
home: Scaffold(
body: ResponseBody(selectedRequestModel: rm),
),
),
),
);
await tester.pumpAndSettle();
final textWidgets = tester.widgetList<Text>(find.byType(Text));
final matchingTextCount = textWidgets
.where((text) =>
text.data != null &&
text.data!.startsWith('data') &&
sseOutput.contains(text.data!.trim()))
.length;
expect(
matchingTextCount,
sseOutput.length,
reason: 'UI does not match all SSE output lines',
);
// Waits for all provider actions to complete before exit
await tester.runAsync(() async {
await Future.delayed(const Duration(seconds: 2));
});
});
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();
});
});
group('CollectionStateNotifier Scripting Tests', () {
late ProviderContainer container;
late CollectionStateNotifier notifier;
setUp(() {
container = createContainer();
notifier = container.read(collectionStateNotifierProvider.notifier);
});
test('should update request with pre-request script', () {
final id = notifier.state!.entries.first.key;
const preRequestScript = '''
ad.request.headers.set('Authorization', 'Bearer ' + ad.environment.get('token'));
ad.request.headers.set('X-Request-ID', 'req-' + Date.now());
ad.console.log('Pre-request script executed');
''';
notifier.update(id: id, preRequestScript: preRequestScript);
final updatedRequest = notifier.getRequestModel(id);
expect(updatedRequest?.preRequestScript, equals(preRequestScript));
});
test('should update request with post-response script', () {
final id = notifier.state!.entries.first.key;
const postResponseScript = '''
if (ad.response.status === 200) {
const data = ad.response.json();
if (data && data.token) {
ad.environment.set('authToken', data.token);
}
}
ad.console.log('Post-response script executed');
''';
notifier.update(id: id, postRequestScript: postResponseScript);
final updatedRequest = notifier.getRequestModel(id);
expect(updatedRequest?.postRequestScript, equals(postResponseScript));
});
test('should preserve scripts when duplicating request', () {
final id = notifier.state!.entries.first.key;
const preRequestScript = 'ad.console.log("Pre-request");';
const postResponseScript = 'ad.console.log("Post-response");';
notifier.update(
id: id,
preRequestScript: preRequestScript,
postRequestScript: postResponseScript,
);
notifier.duplicate(id: id);
final sequence = container.read(requestSequenceProvider);
final duplicatedId = sequence.firstWhere((element) => element != id);
final duplicatedRequest = notifier.getRequestModel(duplicatedId);
expect(duplicatedRequest?.preRequestScript, equals(preRequestScript));
expect(duplicatedRequest?.postRequestScript, equals(postResponseScript));
});
test('should clear scripts when set to empty strings', () {
final id = notifier.state!.entries.first.key;
// First add scripts
notifier.update(
id: id,
preRequestScript: 'ad.console.log("test");',
postRequestScript: 'ad.console.log("test");',
);
// Then clear scripts
notifier.update(
id: id,
preRequestScript: '',
postRequestScript: '',
);
final updatedRequest = notifier.getRequestModel(id);
expect(updatedRequest?.preRequestScript, equals(''));
expect(updatedRequest?.postRequestScript, equals(''));
});
test('should preserve scripts when clearing response', () {
final id = notifier.state!.entries.first.key;
const preRequestScript = 'ad.console.log("Pre-request");';
const postResponseScript = 'ad.console.log("Post-response");';
notifier.update(
id: id,
preRequestScript: preRequestScript,
postRequestScript: postResponseScript,
);
notifier.clearResponse(id: id);
final updatedRequest = notifier.getRequestModel(id);
// Scripts should be preserved when clearing response
expect(updatedRequest?.preRequestScript, equals(preRequestScript));
expect(updatedRequest?.postRequestScript, equals(postResponseScript));
});
test('should handle scripts with special characters and multi-line', () {
final id = notifier.state!.entries.first.key;
const complexPreScript = '''
// Pre-request script with special characters
const apiKey = ad.environment.get('api-key');
if (apiKey) {
ad.request.headers.set('X-API-Key', apiKey);
}
const timestamp = new Date().toISOString();
ad.request.headers.set('X-Timestamp', timestamp);
// Handle Unicode and special characters
ad.request.headers.set('X-Test-Header', '测试_тест_テスト_🚀');
ad.console.log('Complex pre-request script executed ✅');
''';
const complexPostScript = '''
// Post-response script with JSON parsing
try {
const data = ad.response.json();
if (data && data.access_token) {
ad.environment.set('token', data.access_token);
ad.console.log('Token extracted: ' + data.access_token.substring(0, 10) + '...');
}
// Handle different response codes
if (ad.response.status >= 400) {
ad.console.error('Request failed with status: ' + ad.response.status);
ad.environment.set('lastError', 'HTTP ' + ad.response.status);
} else {
ad.environment.unset('lastError');
}
} catch (e) {
ad.console.error('Script error: ' + e.message);
}
''';
notifier.update(
id: id,
preRequestScript: complexPreScript,
postRequestScript: complexPostScript,
);
final updatedRequest = notifier.getRequestModel(id);
expect(updatedRequest?.preRequestScript, equals(complexPreScript));
expect(updatedRequest?.postRequestScript, equals(complexPostScript));
});
test('should handle empty and null scripts gracefully', () {
final id = notifier.state!.entries.first.key;
// Test with empty strings
notifier.update(
id: id,
preRequestScript: '',
postRequestScript: '',
);
var updatedRequest = notifier.getRequestModel(id);
expect(updatedRequest?.preRequestScript, equals(''));
expect(updatedRequest?.postRequestScript, equals(''));
// Test with null values (should maintain existing values)
notifier.update(
id: id,
preRequestScript: 'ad.console.log("test");',
postRequestScript: 'ad.console.log("test");',
);
updatedRequest = notifier.getRequestModel(id);
expect(
updatedRequest?.preRequestScript, equals('ad.console.log("test");'));
expect(
updatedRequest?.postRequestScript, equals('ad.console.log("test");'));
});
test('should save and load scripts correctly', () async {
final id = notifier.state!.entries.first.key;
const preRequestScript = '''
ad.request.headers.set('Authorization', 'Bearer test-token');
ad.environment.set('requestStartTime', Date.now().toString());
''';
const postResponseScript = '''
const data = ad.response.json();
if (data && data.user_id) {
ad.environment.set('currentUserId', data.user_id);
}
''';
notifier.update(
id: id,
preRequestScript: preRequestScript,
postRequestScript: postResponseScript,
);
await notifier.saveData();
// Create new container and load data
late ProviderContainer newContainer;
try {
newContainer = ProviderContainer();
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?.preRequestScript, equals(preRequestScript));
expect(loadedRequest?.postRequestScript, equals(postResponseScript));
} finally {
newContainer.dispose();
}
});
test('should handle scripts in addRequestModel', () {
const preRequestScript = 'ad.console.log("Added request pre-script");';
const postResponseScript = 'ad.console.log("Added request post-script");';
final httpRequestModel = HttpRequestModel(
method: HTTPVerb.post,
url: 'https://api.apidash.dev/data',
headers: const [
NameValueModel(name: 'Content-Type', value: 'application/json'),
],
body: '{"test": true}',
);
// Since addRequestModel takes HttpRequestModel, we'll test scripts through update
notifier.addRequestModel(httpRequestModel, name: 'Test Request');
final sequence = container.read(requestSequenceProvider);
final addedId = sequence.first;
notifier.update(
id: addedId,
preRequestScript: preRequestScript,
postRequestScript: postResponseScript,
);
final addedRequest = notifier.getRequestModel(addedId);
expect(addedRequest?.preRequestScript, equals(preRequestScript));
expect(addedRequest?.postRequestScript, equals(postResponseScript));
});
test('should handle scripts with various JavaScript syntax', () {
final id = notifier.state!.entries.first.key;
const advancedPreScript = r'''
// Advanced JavaScript features
const config = {
apiUrl: ad.environment.get('baseUrl') || 'https://api.apidash.dev/',
timeout: 5000,
retries: 3
};
// Arrow functions and template literals
const generateId = () => 'req_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// Destructuring and modern syntax
const apiUrl = config.apiUrl;
const timeout = config.timeout;
ad.request.url.set(apiUrl + '/v1/users');
ad.request.headers.set('X-Request-ID', generateId());
ad.request.headers.set('X-Timeout', timeout.toString());
// Conditional logic
if (ad.environment.has('debugMode')) {
ad.request.headers.set('X-Debug', 'true');
ad.console.log('Debug mode enabled');
}
// Array operations
const requiredHeaders = ['Authorization', 'Content-Type'];
requiredHeaders.forEach(function(header) {
if (!ad.request.headers.has(header)) {
ad.console.warn('Missing required header: ' + header);
}
});
''';
const advancedPostScript = r'''
// Advanced response processing
try {
const response = ad.response.json();
// Object destructuring
const responseData = response ? response.data : null;
const meta = response ? response.meta : null;
const errors = response ? response.errors : null;
if (errors && Array.isArray(errors)) {
errors.forEach(function(error, index) {
ad.console.error('Error ' + (index + 1) + ': ' + error.message);
});
ad.environment.set('hasErrors', 'true');
} else {
ad.environment.unset('hasErrors');
}
// Handle pagination metadata
if (meta && meta.pagination) {
const page = meta.pagination.page;
const total = meta.pagination.total;
const hasNext = meta.pagination.hasNext;
ad.environment.set('currentPage', page.toString());
ad.environment.set('totalRecords', total.toString());
ad.environment.set('hasNextPage', hasNext.toString());
}
// Extract nested data
if (responseData && Array.isArray(responseData)) {
const activeItems = responseData.filter(function(item) {
return item.status === 'active';
});
ad.environment.set('activeItemCount', activeItems.length.toString());
// Store first active item ID if available
if (activeItems.length > 0) {
ad.environment.set('firstActiveId', activeItems[0].id);
}
}
} catch (parseError) {
ad.console.error('Failed to parse response JSON: ' + parseError.message);
ad.environment.set('parseError', parseError.message);
}
// Response timing analysis
const responseTime = ad.response.time;
if (responseTime) {
ad.environment.set('lastResponseTime', responseTime.toString());
if (responseTime > 2000) {
ad.console.warn('Slow response detected: ' + responseTime + 'ms');
ad.environment.set('slowResponse', 'true');
} else {
ad.environment.unset('slowResponse');
}
}
''';
notifier.update(
id: id,
preRequestScript: advancedPreScript,
postRequestScript: advancedPostScript,
);
final updatedRequest = notifier.getRequestModel(id);
expect(updatedRequest?.preRequestScript, equals(advancedPreScript));
expect(updatedRequest?.postRequestScript, equals(advancedPostScript));
});
test(
'should handle script updates without affecting other request properties',
() {
final id = notifier.state!.entries.first.key;
// First set up a complete request
notifier.update(
id: id,
method: HTTPVerb.post,
url: 'https://api.apidash.dev/test',
headers: const [
NameValueModel(name: 'Content-Type', value: 'application/json'),
NameValueModel(name: 'Accept', value: 'application/json'),
],
body: '{"test": "data"}',
name: 'Test Request',
description: 'A test request with scripts',
);
final beforeRequest = notifier.getRequestModel(id);
// Now update only scripts
const newPreScript = 'ad.console.log("Updated pre-script");';
const newPostScript = 'ad.console.log("Updated post-script");';
notifier.update(
id: id,
preRequestScript: newPreScript,
postRequestScript: newPostScript,
);
final afterRequest = notifier.getRequestModel(id);
// Verify scripts were updated
expect(afterRequest?.preRequestScript, equals(newPreScript));
expect(afterRequest?.postRequestScript, equals(newPostScript));
// Verify other properties were preserved
expect(afterRequest?.httpRequestModel?.method,
equals(beforeRequest?.httpRequestModel?.method));
expect(afterRequest?.httpRequestModel?.url,
equals(beforeRequest?.httpRequestModel?.url));
expect(afterRequest?.httpRequestModel?.headers,
equals(beforeRequest?.httpRequestModel?.headers));
expect(afterRequest?.httpRequestModel?.body,
equals(beforeRequest?.httpRequestModel?.body));
expect(afterRequest?.name, equals(beforeRequest?.name));
expect(afterRequest?.description, equals(beforeRequest?.description));
});
test(
'should not modify original state during script execution - only execution copy',
() {
final id = notifier.state!.entries.first.key;
const preRequestScript = r'''
// Script that modifies request properties
ad.request.headers.set('X-Script-Modified', 'true');
ad.request.headers.set('Authorization', 'Bearer script-token');
ad.request.url.set('https://api.apidash.dev/');
ad.request.params.set('scriptParam', 'scriptValue');
ad.environment.set('scriptExecuted', 'true');
ad.console.log('Pre-request script executed and modified request');
''';
// Set up initial request properties
notifier.update(
id: id,
method: HTTPVerb.get,
url: 'https://api.apidash.dev/api',
headers: const [
NameValueModel(name: 'Content-Type', value: 'application/json'),
NameValueModel(name: 'Accept', value: 'application/json'),
],
params: const [
NameValueModel(name: 'originalParam', value: 'originalValue'),
],
preRequestScript: preRequestScript,
);
// Capture the original state before script execution simulation
final originalRequest = notifier.getRequestModel(id);
final originalHttpRequestModel = originalRequest!.httpRequestModel!;
// Test the script execution isolation by simulating the copyWith pattern used in sendRequest
final executionRequestModel = originalRequest.copyWith();
// Verify that the execution copy is separate from original
expect(executionRequestModel.id, equals(originalRequest.id));
expect(executionRequestModel.httpRequestModel?.url,
equals(originalRequest.httpRequestModel?.url));
expect(executionRequestModel.httpRequestModel?.headers,
equals(originalRequest.httpRequestModel?.headers));
expect(executionRequestModel.httpRequestModel?.params,
equals(originalRequest.httpRequestModel?.params));
// Simulate script modifications on the execution copy
final modifiedExecutionModel = executionRequestModel.copyWith(
httpRequestModel: executionRequestModel.httpRequestModel?.copyWith(
url: 'https://api.apidash.dev/',
headers: [
...originalHttpRequestModel.headers ?? [],
const NameValueModel(name: 'X-Script-Modified', value: 'true'),
const NameValueModel(
name: 'Authorization', value: 'Bearer script-token'),
],
params: [
...originalHttpRequestModel.params ?? [],
const NameValueModel(name: 'scriptParam', value: 'scriptValue'),
],
),
);
// Verify the execution copy has been modified
expect(modifiedExecutionModel.httpRequestModel?.url,
equals('https://api.apidash.dev/'));
expect(
modifiedExecutionModel.httpRequestModel?.headers?.length, equals(4));
final hasScriptModifiedHeader = modifiedExecutionModel
.httpRequestModel?.headers
?.any((header) => header.name == 'X-Script-Modified') ??
false;
expect(hasScriptModifiedHeader, isTrue);
final hasAuthHeader = modifiedExecutionModel.httpRequestModel?.headers
?.any((header) => header.name == 'Authorization') ??
false;
expect(hasAuthHeader, isTrue);
final hasScriptParam = modifiedExecutionModel.httpRequestModel?.params
?.any((param) => param.name == 'scriptParam') ??
false;
expect(hasScriptParam, isTrue);
// Verify that the original request in the state remains completely unchanged
final currentRequest = notifier.getRequestModel(id);
expect(currentRequest?.httpRequestModel?.url,
equals('https://api.apidash.dev/api'));
expect(currentRequest?.httpRequestModel?.headers?.length, equals(2));
expect(currentRequest?.httpRequestModel?.headers?[0].name,
equals('Content-Type'));
expect(currentRequest?.httpRequestModel?.headers?[0].value,
equals('application/json'));
expect(
currentRequest?.httpRequestModel?.headers?[1].name, equals('Accept'));
expect(currentRequest?.httpRequestModel?.headers?[1].value,
equals('application/json'));
expect(currentRequest?.httpRequestModel?.params?.length, equals(1));
expect(currentRequest?.httpRequestModel?.params?[0].name,
equals('originalParam'));
expect(currentRequest?.httpRequestModel?.params?[0].value,
equals('originalValue'));
// Verify no script-modified headers are present in the original state
final hasScriptModifiedHeaderInOriginal = currentRequest
?.httpRequestModel?.headers
?.any((header) => header.name == 'X-Script-Modified') ??
false;
expect(hasScriptModifiedHeaderInOriginal, isFalse);
final hasAuthHeaderInOriginal = currentRequest?.httpRequestModel?.headers
?.any((header) => header.name == 'Authorization') ??
false;
expect(hasAuthHeaderInOriginal, isFalse);
// Verify no script-modified params are present in the original state
final hasScriptParamInOriginal = currentRequest?.httpRequestModel?.params
?.any((param) => param.name == 'scriptParam') ??
false;
expect(hasScriptParamInOriginal, isFalse);
// Verify the script is preserved in the original
expect(currentRequest?.preRequestScript, equals(preRequestScript));
});
tearDown(() {
container.dispose();
});
});
}