use super::*;
mod nestjs_resolver_extract_http {
use super::*;
#[test]
fn joins_controller_prefix_with_get_and_links_the_handler() {
let src = r#"
@Controller('users')
export class UsersController {
@Get()
findAll() { return []; }
}
"#;
let result = NESTJS_RESOLVER.extract("users.controller.ts", src);
assert_eq!(result.nodes.len(), 1);
assert_eq!(result.nodes[0].kind, NodeKind::Route);
assert_eq!(result.nodes[0].name, "GET /users");
assert_eq!(result.references[0].reference_name, "findAll");
assert_eq!(
result.references[0].reference_kind,
ReferenceKind::References
);
assert_eq!(result.references[0].from_node_id, result.nodes[0].id);
}
#[test]
fn joins_controller_prefix_with_a_method_level_path_param() {
let src = r#"
@Controller('cats')
export class CatsController {
@Get(':id')
findOne(@Param('id') id: string) { return id; }
}
"#;
let result = NESTJS_RESOLVER.extract("cats.controller.ts", src);
assert_eq!(result.nodes[0].name, "GET /cats/:id");
assert_eq!(result.references[0].reference_name, "findOne");
}
#[test]
fn handles_an_empty_controller_and_empty_post() {
let src = r#"
@Controller()
export class AppController {
@Post()
create() {}
}
"#;
let result = NESTJS_RESOLVER.extract("app.controller.ts", src);
assert_eq!(result.nodes[0].name, "POST /");
assert_eq!(result.references[0].reference_name, "create");
}
#[test]
fn covers_http_verbs_and_skips_intervening_method_decorators() {
let src = r#"
@Controller('todos')
export class TodosController {
@Put(':id')
@UseGuards(AuthGuard)
update(@Param('id') id: string) {}
@Delete(':id')
async remove(@Param('id') id: string) {}
}
"#;
let result = NESTJS_RESOLVER.extract("todos.controller.ts", src);
assert_eq!(
names(&result.nodes),
vec!["PUT /todos/:id", "DELETE /todos/:id"]
);
assert_eq!(
reference_names(&result.references),
vec!["update", "remove"]
);
}
#[test]
fn attributes_methods_to_the_right_controller_when_a_file_has_two() {
let src = r#"
@Controller('a')
export class AController {
@Get('x')
ax() {}
}
@Controller('b')
export class BController {
@Get('y')
by() {}
}
"#;
let result = NESTJS_RESOLVER.extract("multi.controller.ts", src);
assert_eq!(names(&result.nodes), vec!["GET /a/x", "GET /b/y"]);
}
}
mod nestjs_resolver_extract_graphql {
use super::*;
#[test]
fn emits_query_mutation_nodes_from_a_resolver_defaulting_to_the_method_name() {
let src = r#"
@Resolver(() => User)
export class UsersResolver {
@Query(() => [User])
users() { return []; }
@Mutation(() => User)
createUser(@Args('input') input: CreateUserInput) {}
}
"#;
let result = NESTJS_RESOLVER.extract("users.resolver.ts", src);
assert_eq!(
names(&result.nodes),
vec!["QUERY users", "MUTATION createUser"]
);
assert_eq!(
reference_names(&result.references),
vec!["users", "createUser"]
);
}
#[test]
fn uses_an_explicit_operation_name_when_given() {
let src = r#"
@Resolver()
export class CatsResolver {
@Query(() => Cat, { name: 'cat' })
getCat() {}
}
"#;
let result = NESTJS_RESOLVER.extract("cats.resolver.ts", src);
assert_eq!(result.nodes[0].name, "QUERY cat");
}
#[test]
fn does_not_treat_the_rest_query_parameter_decorator_as_a_graphql_op() {
let src = r#"
@Controller('search')
export class SearchController {
@Get()
search(@Query() query: SearchDto) { return query; }
}
"#;
let result = NESTJS_RESOLVER.extract("search.controller.ts", src);
assert_eq!(names(&result.nodes), vec!["GET /search"]);
}
}
mod nestjs_resolver_extract_microservices_websockets {
use super::*;
#[test]
fn extracts_message_pattern_and_event_pattern_handlers() {
let src = r#"
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
accumulate(data: number[]) {}
@EventPattern('user.created')
handleUserCreated(data: any) {}
}
"#;
let result = NESTJS_RESOLVER.extract("math.controller.ts", src);
assert_eq!(
names(&result.nodes),
vec!["MESSAGE sum", "EVENT user.created"]
);
assert_eq!(
reference_names(&result.references),
vec!["accumulate", "handleUserCreated"]
);
}
#[test]
fn extracts_subscribe_message_handlers_with_the_gateway_namespace() {
let src = r#"
@WebSocketGateway({ namespace: 'chat' })
export class ChatGateway {
@SubscribeMessage('message')
handleMessage(@MessageBody() data: string) {}
}
"#;
let result = NESTJS_RESOLVER.extract("chat.gateway.ts", src);
assert_eq!(result.nodes[0].name, "WS chat:message");
assert_eq!(result.references[0].reference_name, "handleMessage");
}
#[test]
fn extracts_subscribe_message_without_a_namespace() {
let src = r#"
@WebSocketGateway()
export class EventsGateway {
@SubscribeMessage('events')
onEvent() {}
}
"#;
let result = NESTJS_RESOLVER.extract("events.gateway.ts", src);
assert_eq!(result.nodes[0].name, "WS events");
}
#[test]
fn returns_empty_for_a_non_js_ts_file() {
let result = NESTJS_RESOLVER.extract("thing.py", "@Controller(\"x\")");
assert!(result.nodes.is_empty());
assert!(result.references.is_empty());
}
}
mod nestjs_resolver_detect {
use super::*;
#[test]
fn detects_nestjs_in_package_json() {
let mut context = MockResolutionContext::new().with_file_contents(&[(
"package.json",
r#"{"dependencies":{"@nestjs/common":"^10.0.0"}}"#,
)]);
assert!(NESTJS_RESOLVER.detect(&mut context));
}
#[test]
fn detects_controller_in_controller_ts_file_when_package_json_is_absent() {
let mut context = MockResolutionContext::new()
.with_file_contents(&[(
"src/users.controller.ts",
"@Controller('users')\nexport class UsersController {}",
)])
.with_all_files(&["src/users.controller.ts"]);
assert!(NESTJS_RESOLVER.detect(&mut context));
}
#[test]
fn returns_false_for_a_non_nest_project() {
let mut context = MockResolutionContext::new()
.with_file_contents(&[("package.json", r#"{"dependencies":{"express":"^4"}}"#)]);
assert!(!NESTJS_RESOLVER.detect(&mut context));
}
}
mod nestjs_resolver_resolve {
use super::*;
#[test]
fn resolves_an_injected_service_reference_to_the_class_in_a_service_ts_file() {
let svc_node = node(
"class:src/users/users.service.ts:UsersService:3",
NodeKind::Class,
"UsersService",
"src/users/users.service.ts::UsersService",
"src/users/users.service.ts",
Language::TypeScript,
3,
3,
);
let mut context = MockResolutionContext::with_nodes(vec![svc_node.clone()]);
let reference = unresolved_ref(
"class:src/users/users.controller.ts:UsersController:5",
"UsersService",
ReferenceKind::References,
"src/users/users.controller.ts",
Language::TypeScript,
);
let result = NESTJS_RESOLVER.resolve(&reference, &mut context);
let result = result.expect("UsersService should resolve");
assert_eq!(result.target_node_id, svc_node.id);
assert_eq!(result.resolved_by, ResolvedBy::Framework);
assert!(result.confidence >= 0.85);
}
#[test]
fn returns_null_for_a_name_without_a_provider_suffix() {
let mut context = MockResolutionContext::new();
let reference = unresolved_ref(
"x",
"doThing",
ReferenceKind::References,
"a.ts",
Language::TypeScript,
);
assert!(NESTJS_RESOLVER.resolve(&reference, &mut context).is_none());
}
}