diff --git a/.github/actions/publish-report/action.yml b/.github/actions/publish-report/action.yml new file mode 100644 index 0000000..a5811e4 --- /dev/null +++ b/.github/actions/publish-report/action.yml @@ -0,0 +1,35 @@ +name: Publish JUnit Report (Short Names) +description: Normalize JUnit XML report names and publish a summary-only test report. +inputs: + token: + description: GitHub token for creating the check run. + required: true + report-name: + description: Name shown for the test report. + required: false + default: Summary of JUnit Tests + +runs: + using: composite + steps: + - name: Normalize JUnit report names + shell: bash + run: | + mkdir -p junit-short + shopt -s globstar nullglob + for f in **/target/*-reports/TEST-*.xml; do + base="$(basename "$f")" + short="${base#TEST-}" + short="${short%.xml}" + cp "$f" "junit-short/${short}" + done + + - name: Publish Test Report + uses: dorny/test-reporter@v2 + with: + token: ${{ inputs.token }} + name: ${{ inputs.report-name }} + path: "*" + reporter: java-junit + only-summary: true + working-directory: "junit-short" diff --git a/.github/actions/setup-java-maven/action.yml b/.github/actions/setup-java-maven/action.yml new file mode 100644 index 0000000..fe1d914 --- /dev/null +++ b/.github/actions/setup-java-maven/action.yml @@ -0,0 +1,29 @@ +name: "Setup Maven with GitHub Packages" + +description: "Sets up JDK, caches Maven dependencies, and configures GitHub Packages for Maven repositories." + +inputs: + java-version: + description: "Java version to install" + default: "17" + required: true + java-distribution: + description: "Java distribution to use" + default: "corretto" + +runs: + using: "composite" + steps: + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: ${{ inputs.java-distribution }} + java-version: ${{ inputs.java-version }} + + - name: Cache Maven Repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- \ No newline at end of file diff --git a/.github/workflows/build-and-analyse.yml b/.github/workflows/build-and-analyse.yml new file mode 100644 index 0000000..a8952c2 --- /dev/null +++ b/.github/workflows/build-and-analyse.yml @@ -0,0 +1,41 @@ +name: Build and Analyse + +on: + push: + branches: + - develop + - feature* + pull_request: + types: [ opened, synchronize, reopened ] + +defaults: + run: + shell: bash + +jobs: + + build-and-analyse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + + - name: Setup Java and Maven + uses: ./.github/actions/setup-java-maven + + - name: Build and analyze with SonarCloud + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + CF_API_TOKEN: ${{ secrets.API_TOKEN }} + run: | + echo "Running SonarCloud analysis..." + mvn -B -DtestClasspath=src/test/ verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=th-schwarz_CloudflareDNS-java + + - name: Publish Test Report + uses: ./.github/actions/publish-report/ + if: ${{ always() }} + with: + token: ${{ secrets.GITHUB_TOKEN }} + report-name: Summary of JUnit Tests diff --git a/.woodpecker/maven.yml b/.woodpecker/maven.yml index 6cd51bf..da10043 100644 --- a/.woodpecker/maven.yml +++ b/.woodpecker/maven.yml @@ -4,10 +4,10 @@ when: branch: develop steps: - - name: hello - image: alpine - commands: - - echo "Hello World!" +# - name: hello +# image: alpine +# commands: +# - echo "Hello World!" - name: maven verify image: maven:3-amazoncorretto-17-alpine diff --git a/README.md b/README.md index c229358..fda986b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ [![pipeline-badge](https://ci.codeberg.org/api/badges/16522/status.svg?events=push%2Cmanual%2Cpull_request%2Cpull_request_closed)](https://ci.codeberg.org/repos/16522) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=th-schwarz_CloudflareDNS-java&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=th-schwarz_CloudflareDNS-java) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=th-schwarz_CloudflareDNS-java&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=th-schwarz_CloudflareDNS-java) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=th-schwarz_CloudflareDNS-java&metric=coverage)](https://sonarcloud.io/summary/new_code?id=th-schwarz_CloudflareDNS-java) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=th-schwarz_CloudflareDNS-java&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=th-schwarz_CloudflareDNS-java) +[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=th-schwarz_CloudflareDNS-java&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=th-schwarz_CloudflareDNS-java) + +[![codeberg.png](docs/codeberg.png)](https://codeberg.org/th-schwarz/CloudflareDNS-java) + ## Preface This project provides a java client for minimalistic access to the Cloudflare API version 4, which is mainly used for @@ -42,6 +50,9 @@ The dependency is: ## Changelog +- 0.4.0-SNAPSHOT: + - **Breaking Change**: renamed `client.zone().record()` to `client.zone().getRecord()` + - Code quality improvements: Increasing test coverage - 0.3.0: - **Breaking Change**: - **New Fluent API**: Changed the initialization of the client(`new CfDnsClientBuilder().withApiTokenAuth("your-api-token").build()`) @@ -54,7 +65,7 @@ The dependency is: - RecordEntity getter methods renamed for clarity: `getName()` → `getSld()` - **New Fluent API**: Changed the initialization of the client(`new CfDnsClientBuilder().withApiTokenAuth("your-api-token").build()`) and added chainable method interface for more readable DNS operations ( `client.zone().record()...`) - - Code quality improvements: eliminated duplication in batch operations, improved type safety in HTTP methods, + - Code quality improvements: removed duplication in batch operations, improved type safety in HTTP methods, optimized string concatenation, removed mutable setters from CfDnsClient - Enhanced type validation in `RecordEntity.build()` with better error messages - CfClient#recordList must return multiple RecordEntries @@ -359,7 +370,7 @@ that reduces verbosity and improves code readability. ### Basic Usage ```java -// Create a DNS record +// Create a DNS getRecord client.zone("example.com") .record("api") .create(RecordType.A, "192.168.1.1",60); @@ -373,7 +384,7 @@ List records = client.zone("example.com") List zoneRecords = client.zone("example.com") .list(RecordType.A, RecordType.AAAA); -// Update a DNS record +// Update a DNS getRecord RecordEntity updated = client.zone("example.com") .record("api", RecordType.A) .update("192.168.1.2"); @@ -398,7 +409,7 @@ CfDnsClient client = new CfDnsClientBuilder() .withApiTokenAuth("your-api-token") .build(); -// Create a new record +// Create a new getRecord client.zone("example.com") .record("api") .create(RecordType.A, "192.168.100.1",60); @@ -409,7 +420,7 @@ List records = client.zone("example.com") .get(); System.out.println("IP: "+records.get(0).getContent()); -// Update the record +// Update the getRecord client.zone("example.com") .record("api",RecordType.A) .update("192.168.100.2"); diff --git a/docs/codeberg.png b/docs/codeberg.png new file mode 100644 index 0000000..a36522b Binary files /dev/null and b/docs/codeberg.png differ diff --git a/pom.xml b/pom.xml index 7bc7faf..037f23e 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,15 @@ 5.14.2 5.21.0 + + th-schwarz + https://sonarcloud.io + ${file.encoding} + th-schwarz_CloudflareDNS-java + CloudflareDNS-java + develop + src/test/java/**/* + 1.18.20.0 @@ -273,7 +282,6 @@ - diff --git a/src/main/java/codes/thischwa/cf/CfBasicHttpClient.java b/src/main/java/codes/thischwa/cf/CfBasicHttpClient.java index c51f6f5..6c8c6a4 100644 --- a/src/main/java/codes/thischwa/cf/CfBasicHttpClient.java +++ b/src/main/java/codes/thischwa/cf/CfBasicHttpClient.java @@ -33,6 +33,9 @@ abstract class CfBasicHttpClient { private final CfDnsClientBuilder.CfAuth auth; private final ObjectMapper objectMapper; + private record ResultWrapper(int statusCode, String responseBody) { + } + /** * Creates a new Cloudflare HTTP client with the specified base URL and authentication. * @@ -97,7 +100,7 @@ abstract class CfBasicHttpClient { log.error("JSON parsing error for request to {}", logUri, e); throw new CloudflareApiException("Error processing JSON response", e); } catch (Exception e) { - throw new CloudflareApiException("Server error!", e); + throw new CloudflareApiException("Unexpected error!", e); } } @@ -187,7 +190,4 @@ abstract class CfBasicHttpClient { private String buildUrl(String endpoint) { return baseUrl + endpoint; } - - private record ResultWrapper(int statusCode, String responseBody) { - } } diff --git a/src/main/java/codes/thischwa/cf/CfDnsClient.java b/src/main/java/codes/thischwa/cf/CfDnsClient.java index 4a1490a..f159a62 100644 --- a/src/main/java/codes/thischwa/cf/CfDnsClient.java +++ b/src/main/java/codes/thischwa/cf/CfDnsClient.java @@ -116,14 +116,14 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Provides fluent API access to operations on a specific zone. + * Provides fluent API access to operations in a specific zone. * This method returns a ZoneOperations interface that allows chaining operations * on DNS records within the specified zone. * *

Example: *


    * client.zone("example.com")
-   *       .record("api")
+   *       .getRecord("api")
    *       .create(RecordType.A, "192.168.1.1", 60);
    * 
* @@ -177,6 +177,33 @@ public class CfDnsClient extends CfBasicHttpClient { return response.getResult().get(0); } + /** + * Retrieves a list of DNS records for a specified zone, with optional paging support. + * + * @param zone The zone entity containing information about the target zone. + * @return A list of RecordEntity objects representing the DNS records of the specified zone. + * @throws CloudflareApiException If an error occurs during the API request or response processing. + */ + public List recordList(ZoneEntity zone) throws CloudflareApiException { + return recordList(zone, (PagingRequest) null); + } + + /** + * Retrieves a list of DNS records for a specified zone, with optional paging support. + * + * @param zone The zone entity containing information about the target zone. + * @param pagingRequest The paging request containing parameters such as page size and number. + * @return A list of RecordEntity objects representing the DNS records of the specified zone. + * @throws CloudflareApiException If an error occurs during the API request or response processing. + */ + public List recordList(ZoneEntity zone, @Nullable PagingRequest pagingRequest) throws CloudflareApiException { + PagingRequest pr = pagingRequest == null ? PagingRequest.defaultPaging() : pagingRequest; + String endpoint = pr.addQueryString(CfRequest.RECORD_LIST.buildPath(zone.getId())); + RecordMultipleResponse resp = getRequest(endpoint, RecordMultipleResponse.class); + checkResponse(resp); + return resp.getResult(); + } + /** * Retrieves DNS records for the specified second-level domain (SLD) within a zone. * @@ -192,49 +219,31 @@ public class CfDnsClient extends CfBasicHttpClient { /** * Retrieves DNS records for the specified second-level domain (SLD) within a zone. - * Optionally filters by one or more DNS record types. + * Optionally, filters by one or more DNS getRecord types. * * @param zone The zone entity containing information about the domain zone. * @param sld The second-level domain (SLD) for which to retrieve DNS records. - * @param types Optional parameter specifying one or more DNS record types to filter the results. + * @param types Optional parameter specifying one or more DNS getRecord types to filter the results. * @return A list of {@code RecordEntity} objects representing the DNS records for the specified domain. * @throws CloudflareNotFoundException if the specified SLD is not found in the zone * @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API */ public List recordList(ZoneEntity zone, String sld, @Nullable RecordType... types) throws CloudflareApiException { - PagingRequest pagingRequest = PagingRequest.defaultPaging(); - List recs = recordList(zone, sld, pagingRequest); - return filterAndSetZoneRecords(zone, types, recs); - } - - - /** - * Retrieves all record entities for a specific second-level domain (SLD) within a given DNS - * zone using the provided paging request parameters. - * - * @param zone The DNS zone entity for which the SLD records are to be fetched. - * @param sld The second-level domain name for which the records are retrieved. - * @param pagingRequest The paging request. - * @return A list of {@code RecordEntity} associated with the desired SLD. - * @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API. - */ - public List recordList(ZoneEntity zone, String sld, PagingRequest pagingRequest) - throws CloudflareApiException { String fqdn = buildFqdn(zone, sld); - String endpoint = - pagingRequest.addQueryString(CfRequest.RECORD_INFO_NAME.buildPath(zone.getId(), fqdn)); + String endpoint = CfRequest.RECORD_LIST_NAME.buildPath(zone.getId(), fqdn); RecordMultipleResponse resp = getRequest(endpoint, RecordMultipleResponse.class); - checkResponse(resp); - return resp.getResult(); + checkResponse(resp, false); + List recs = resp.getResult(); + return filterAndSetZoneRecords(zone, types, recs); } /** * Retrieves a list of all DNS records for a given zone. - * Optionally filters by one or more DNS record types. + * Optionally, filters by one or more DNS getRecord types. * * @param zone The zone entity containing information about the domain zone. - * @param types Optional parameter specifying one or more DNS record types to filter the results. + * @param types Optional parameter specifying one or more DNS getRecord types to filter the results. * @return A list of {@code RecordEntity} objects representing the DNS records for the specified zone. * @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API */ @@ -248,17 +257,17 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Creates a new DNS record for a given second-level domain (SLD) within the specified zone. + * Creates a new DNS getRecord for a given second-level domain (SLD) within the specified zone. * - * @param zone The ZoneEntity representing the DNS zone where the record is to be created. - * @param sld The second-level domain (SLD) for which the DNS record is being created. - * @param ttl The time-to-live (TTL) value for the DNS record in seconds. - * @param type The RecordType specifying the type of the DNS record (e.g., A, AAAA, CNAME). - * @param content The content of the DNS record (e.g., IP address for A/AAAA records, target + * @param zone The ZoneEntity representing the DNS zone where the getRecord is to be created. + * @param sld The second-level domain (SLD) for which the DNS getRecord is being created. + * @param ttl The time-to-live (TTL) value for the DNS getRecord in seconds. + * @param type The RecordType specifying the type of the DNS getRecord (e.g., A, AAAA, CNAME). + * @param content The content of the DNS getRecord (e.g., IP address for A/AAAA records, target * domain for CNAME). - * @return The created RecordEntity object containing details of the newly created DNS record. + * @return The created RecordEntity object containing details of the newly created DNS getRecord. * @throws CloudflareApiException If an error occurs while communicating with the Cloudflare API - * or creating the record. + * or creating the getRecord. */ public RecordEntity recordCreateSld(ZoneEntity zone, String sld, int ttl, RecordType type, String content) throws CloudflareApiException { @@ -267,14 +276,14 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Creates a DNS record in the specified DNS zone with the provided details. + * Creates a DNS getRecord in the specified DNS zone with the provided details. * - * @param zone the DNS zone in which the record will be created - * @param name the name of the DNS record (e.g., www.example.com) - * @param ttl the time-to-live (TTL) value for the DNS record - * @param type the type of the DNS record (e.g., A, AAAA, CNAME) - * @param content the content or value of the DNS record - * @return the created DNS record as a {@link RecordEntity} object + * @param zone the DNS zone in which the getRecord will be created + * @param name the name of the DNS getRecord (e.g., www.example.com) + * @param ttl the time-to-live (TTL) value for the DNS getRecord + * @param type the type of the DNS getRecord (e.g., A, AAAA, CNAME) + * @param content the content or value of the DNS getRecord + * @return the created DNS getRecord as a {@link RecordEntity} object * @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API */ public RecordEntity recordCreate(ZoneEntity zone, String name, int ttl, RecordType type, @@ -284,13 +293,13 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Creates a new DNS record in the specified zone using the Cloudflare API. + * Creates a new DNS getRecord in the specified zone using the Cloudflare API. * - * @param zone The zone entity where the record will be created. Contains details such as zone + * @param zone The zone entity where the getRecord will be created. Contains details such as zone * ID. - * @param rec The record entity representing the DNS record to be created, including its + * @param rec The getRecord entity representing the DNS getRecord to be created, including its * attributes. - * @return The created record entity as returned by the Cloudflare API. + * @return The created getRecord entity as returned by the Cloudflare API. * @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API. */ public RecordEntity recordCreate(ZoneEntity zone, RecordEntity rec) @@ -305,11 +314,11 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Deletes a DNS record of the specified type within a given zone on the Cloudflare API. + * Deletes a DNS getRecord of the specified type within a given zone on the Cloudflare API. * - * @param zone The zone entity that specifies the zone in which the record exists. - * @param rec The record entity that represents the DNS record to be deleted. - * @return {@code true} if the DNS record was successfully deleted; {@code false} otherwise. + * @param zone The zone entity that specifies the zone in which the getRecord exists. + * @param rec The getRecord entity that represents the DNS getRecord to be deleted. + * @return {@code true} if the DNS getRecord was successfully deleted; {@code false} otherwise. * @throws CloudflareApiException if there is an issue during the API communication, or the * request fails for any reason. */ @@ -324,11 +333,11 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Deletes a DNS record of the specified type within a given zone on the Cloudflare API. + * Deletes a DNS getRecord of the specified type within a given zone on the Cloudflare API. * - * @param zone The zone entity that specifies the zone in which the record exists. - * @param id The record entity that represents the DNS record to be deleted. - * @return {@code true} if the DNS record was successfully deleted; {@code false} otherwise. + * @param zone The zone entity that specifies the zone in which the getRecord exists. + * @param id The getRecord entity that represents the DNS getRecord to be deleted. + * @return {@code true} if the DNS getRecord was successfully deleted; {@code false} otherwise. * @throws CloudflareApiException if there is an issue during the API communication or the request * fails for any reason. */ @@ -341,12 +350,12 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Updates an existing DNS record in a specified Cloudflare zone. + * Updates an existing DNS getRecord in a specified Cloudflare zone. * * @param zone the zone entity containing the ID of the target zone - * @param rec the record entity containing the ID of the DNS record to be updated and its updated + * @param rec the getRecord entity containing the ID of the DNS getRecord to be updated and its updated * data - * @return the updated record entity as returned by the Cloudflare API + * @return the updated getRecord entity as returned by the Cloudflare API * @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API */ public RecordEntity recordUpdate(ZoneEntity zone, RecordEntity rec) @@ -362,11 +371,11 @@ public class CfDnsClient extends CfBasicHttpClient { } /** - * Deletes DNS records of a specific type within a given zone if they exist. If no record of the + * Deletes DNS records of a specific type within a given zone if they exist. If no getRecord of the * specified type exists, it logs this occurrence without throwing an exception. * - * @param zone The DNS zone entity in which the record exists. - * @param sld The second-level domain for which the record is being checked. + * @param zone The DNS zone entity in which the getRecord exists. + * @param sld The second-level domain for which the getRecord is being checked. * @param recordTypes The types of DNS records that should be deleted if they exist. * @throws CloudflareApiException If an error occurs during API communication. */ @@ -377,7 +386,7 @@ public class CfDnsClient extends CfBasicHttpClient { try { recs = recordList(zone, sld, recordTypes); } catch (CloudflareNotFoundException e) { - log.trace("No record of type {} found for domain {}.", recordTypes, fqdn); + log.trace("No getRecord of type {} found for domain {}.", recordTypes, fqdn); return; } for (RecordEntity rec : recs) { @@ -385,13 +394,13 @@ public class CfDnsClient extends CfBasicHttpClient { recordDelete(zone, rec); log.info("Record {} of type {} successful deleted.", fqdn, recordTypes); } catch (CloudflareApiException e) { - log.error("Failed to delete record {} of type {} for zone {}: {}", fqdn, recordTypes, zone.getName(), e.getMessage()); + log.error("Failed to delete getRecord {} of type {} for zone {}: {}", fqdn, recordTypes, zone.getName(), e.getMessage()); } } } /** - * Processes a batch of DNS record operations (POST, PUT, PATCH, DELETE) for a specified zone. + * Processes a batch of DNS getRecord operations (POST, PUT, PATCH, DELETE) for a specified zone. * This method builds and cleans the input records, sends the batch request to the Cloudflare API, * and returns a result containing processed batch entries. * @@ -407,7 +416,7 @@ public class CfDnsClient extends CfBasicHttpClient { @Nullable List patchRecords, @Nullable List deleteRecords) throws CloudflareApiException { BatchEntry batchEntry = new BatchEntry(); - // build 'clean' record entries + // build 'clean' getRecord entries if (postRecords != null) { batchEntry.setPosts(cleanRecordsForPostOrPut(postRecords)); } @@ -437,8 +446,7 @@ public class CfDnsClient extends CfBasicHttpClient { if (types != null && types.length > 0) { Set allowedTypes = new HashSet<>(Arrays.asList(types)); filtered = recs.stream() - .filter(rec -> allowedTypes.contains(RecordType.valueOf(rec.getType()))) - .collect(Collectors.toList());; + .filter(rec -> allowedTypes.contains(RecordType.valueOf(rec.getType()))).toList(); } else { filtered = new ArrayList<>(recs); } diff --git a/src/main/java/codes/thischwa/cf/CfDnsClientBuilder.java b/src/main/java/codes/thischwa/cf/CfDnsClientBuilder.java index bec5da4..c779b68 100644 --- a/src/main/java/codes/thischwa/cf/CfDnsClientBuilder.java +++ b/src/main/java/codes/thischwa/cf/CfDnsClientBuilder.java @@ -22,19 +22,6 @@ public class CfDnsClientBuilder { @Nullable private String baseUrl; - /** - * Constructs a new instance of `CfDnsClientBuilder`. - * - *

This class serves as a builder for creating and configuring instances of a CfDnsClient. It provides - * a fluent API to set various optional configurations, such as API authentication methods and base - * URL, before constructing the client. - * - *

By using this constructor, you can initiate the building process with default settings, which can - * later be overridden using the provided builder methods. - */ - public CfDnsClientBuilder() { - } - /** * Configures whether an exception should be thrown when an empty result is encountered * during operations performed by the `CfDnsClient`. diff --git a/src/main/java/codes/thischwa/cf/CfRequest.java b/src/main/java/codes/thischwa/cf/CfRequest.java index dcf0fe0..c2573de 100644 --- a/src/main/java/codes/thischwa/cf/CfRequest.java +++ b/src/main/java/codes/thischwa/cf/CfRequest.java @@ -27,24 +27,23 @@ public enum CfRequest { */ RECORD_LIST("/zones/%s/dns_records"), /** - * Represents the API endpoint path for creating a new DNS record within a specific DNS zone. The + * Represents the API endpoint path for retrieving information about a DNS getRecord within a + * specific DNS zone by its name. The endpoint path includes placeholders for the zone identifier + * and the getRecord name, which need to be provided to construct the complete path. + */ + RECORD_LIST_NAME("/zones/%s/dns_records?name=%s"), + /** + * Represents the API endpoint path for creating a new DNS getRecord within a specific DNS zone. The * endpoint path includes a placeholder for the zone identifier, which needs to be provided to * construct the complete path. */ RECORD_CREATE("/zones/%s/dns_records"), /** - * Represents the API endpoint path for retrieving information about a DNS record within a - * specific DNS zone by its name. The endpoint path includes placeholders for the zone identifier - * and the record name, which need to be provided to construct the complete path. - */ - RECORD_INFO_NAME("/zones/%s/dns_records?name=%s"), - /** - * Represents the API endpoint path for updating an existing DNS record within a specific DNS - * zone. The endpoint path includes placeholders for the zone identifier and the record + * Represents the API endpoint path for updating an existing DNS getRecord within a specific DNS + * zone. The endpoint path includes placeholders for the zone identifier and the getRecord * identifier, which need to be provided to construct the complete path. */ RECORD_UPDATE("/zones/%s/dns_records/%s"), - /** * Represents the API endpoint path for performing batch operations on DNS records within a specific zone. * The placeholder "%s" in the path is intended to be replaced by a zone identifier. @@ -52,8 +51,8 @@ public enum CfRequest { */ RECORD_BATCH("/zones/%s/dns_records/batch"), /** - * Represents the API endpoint path for deleting an existing DNS record within a specific DNS - * zone. The endpoint path includes placeholders for the zone identifier and the record + * Represents the API endpoint path for deleting an existing DNS getRecord within a specific DNS + * zone. The endpoint path includes placeholders for the zone identifier and the getRecord * identifier, which need to be provided to construct the complete path. */ RECORD_DELETE("/zones/%s/dns_records/%s"); diff --git a/src/main/java/codes/thischwa/cf/CloudflareNotFoundException.java b/src/main/java/codes/thischwa/cf/CloudflareNotFoundException.java index ce744bc..c52e592 100644 --- a/src/main/java/codes/thischwa/cf/CloudflareNotFoundException.java +++ b/src/main/java/codes/thischwa/cf/CloudflareNotFoundException.java @@ -18,17 +18,4 @@ public class CloudflareNotFoundException extends CloudflareApiException { public CloudflareNotFoundException(String message) { super(message); } - - /** - * Constructs a new CloudflareNotFoundException with the specified detail message and cause. - * - * @param message the detail message, which provides additional context about the "not found" - * error encountered during interaction with the Cloudflare API. - * @param cause the cause of this exception, which is the underlying throwable that triggered this - * exception. - */ - public CloudflareNotFoundException(String message, Throwable cause) { - super(message, cause); - } - } diff --git a/src/main/java/codes/thischwa/cf/fluent/RecordOperations.java b/src/main/java/codes/thischwa/cf/fluent/RecordOperations.java index 579300b..281d662 100644 --- a/src/main/java/codes/thischwa/cf/fluent/RecordOperations.java +++ b/src/main/java/codes/thischwa/cf/fluent/RecordOperations.java @@ -6,7 +6,7 @@ import codes.thischwa.cf.model.RecordType; import java.util.List; /** - * Fluent interface for record-level operations. + * Fluent interface for getRecord-level operations. * Provides a chainable API for CRUD operations on DNS records. */ public interface RecordOperations { @@ -20,29 +20,29 @@ public interface RecordOperations { List get() throws CloudflareApiException; /** - * Creates a new DNS record with the specified parameters. + * Creates a new DNS getRecord with the specified parameters. * - * @param type the DNS record type (e.g., A, AAAA, CNAME) - * @param content the content of the DNS record (e.g., IP address) + * @param type the DNS getRecord type (e.g., A, AAAA, CNAME) + * @param content the content of the DNS getRecord (e.g., IP address) * @param ttl the time-to-live value in seconds * @return the created RecordEntity - * @throws CloudflareApiException if an error occurs while creating the record + * @throws CloudflareApiException if an error occurs while creating the getRecord */ RecordEntity create(RecordType type, String content, int ttl) throws CloudflareApiException; /** - * Updates an existing DNS record with new content. + * Updates an existing DNS getRecord with new content. * - * @param newContent the new content for the DNS record + * @param newContent the new content for the DNS getRecord * @return the updated RecordEntity - * @throws CloudflareApiException if an error occurs while updating the record + * @throws CloudflareApiException if an error occurs while updating the getRecord */ RecordEntity update(String newContent) throws CloudflareApiException; /** * Deletes DNS records of the specified types. * - * @param types the DNS record types to delete + * @param types the DNS getRecord types to delete * @throws CloudflareApiException if an error occurs while deleting records */ void delete(RecordType... types) throws CloudflareApiException; diff --git a/src/main/java/codes/thischwa/cf/fluent/RecordOperationsImpl.java b/src/main/java/codes/thischwa/cf/fluent/RecordOperationsImpl.java index da9a5b7..eebe8d6 100644 --- a/src/main/java/codes/thischwa/cf/fluent/RecordOperationsImpl.java +++ b/src/main/java/codes/thischwa/cf/fluent/RecordOperationsImpl.java @@ -9,7 +9,7 @@ import java.util.List; import org.jetbrains.annotations.Nullable; /** - * Implementation of RecordOperations for fluent API access to record-level operations. + * Implementation of RecordOperations for fluent API access to getRecord-level operations. */ public class RecordOperationsImpl implements RecordOperations { diff --git a/src/main/java/codes/thischwa/cf/fluent/ZoneOperations.java b/src/main/java/codes/thischwa/cf/fluent/ZoneOperations.java index 5056351..2c8c21e 100644 --- a/src/main/java/codes/thischwa/cf/fluent/ZoneOperations.java +++ b/src/main/java/codes/thischwa/cf/fluent/ZoneOperations.java @@ -17,7 +17,7 @@ public interface ZoneOperations { * @return a RecordOperations instance for chaining record-specific operations * @throws CloudflareApiException if the zone cannot be found or accessed */ - RecordOperations record(String sld) throws CloudflareApiException; + RecordOperations getRecord(String sld) throws CloudflareApiException; /** * Selects a record with specific types within the zone for further operations. @@ -27,7 +27,7 @@ public interface ZoneOperations { * @return a RecordOperations instance for chaining record-specific operations * @throws CloudflareApiException if the zone cannot be found or accessed */ - RecordOperations record(String sld, @Nullable RecordType... types) throws CloudflareApiException; + RecordOperations getRecord(String sld, @Nullable RecordType... types) throws CloudflareApiException; /** * Lists all DNS records within the zone, optionally filtered by types. diff --git a/src/main/java/codes/thischwa/cf/fluent/ZoneOperationsImpl.java b/src/main/java/codes/thischwa/cf/fluent/ZoneOperationsImpl.java index 97bdae4..fd78f28 100644 --- a/src/main/java/codes/thischwa/cf/fluent/ZoneOperationsImpl.java +++ b/src/main/java/codes/thischwa/cf/fluent/ZoneOperationsImpl.java @@ -28,12 +28,12 @@ public class ZoneOperationsImpl implements ZoneOperations { } @Override - public RecordOperations record(String sld) throws CloudflareApiException { + public RecordOperations getRecord(String sld) throws CloudflareApiException { return new RecordOperationsImpl(client, zone, sld, null); } @Override - public RecordOperations record(String sld, @Nullable RecordType... types) throws CloudflareApiException { + public RecordOperations getRecord(String sld, @Nullable RecordType... types) throws CloudflareApiException { return new RecordOperationsImpl(client, zone, sld, types); } diff --git a/src/main/java/codes/thischwa/cf/fluent/package-info.java b/src/main/java/codes/thischwa/cf/fluent/package-info.java index 8478e70..511ec69 100644 --- a/src/main/java/codes/thischwa/cf/fluent/package-info.java +++ b/src/main/java/codes/thischwa/cf/fluent/package-info.java @@ -6,24 +6,24 @@ * *

Example usage: *


- * // Create a DNS record
+ * // Create a DNS getRecord
  * client.zone("example.com")
- *       .record("api")
+ *       .getRecord("api")
  *       .create(RecordType.A, "192.168.1.1", 60);
  *
  * // Get DNS records
  * List<RecordEntity> records = client.zone("example.com")
- *                                      .record("www", RecordType.A)
+ *                                      .getRecord("www", RecordType.A)
  *                                      .get();
  *
- * // Update a DNS record
+ * // Update a DNS getRecord
  * client.zone("example.com")
- *       .record("api", RecordType.A)
+ *       .getRecord("api", RecordType.A)
  *       .update("192.168.1.2");
  *
  * // Delete DNS records
  * client.zone("example.com")
- *       .record("old-service")
+ *       .getRecord("old-service")
  *       .delete(RecordType.A, RecordType.AAAA);
  * 
*/ diff --git a/src/main/java/codes/thischwa/cf/model/BatchEntry.java b/src/main/java/codes/thischwa/cf/model/BatchEntry.java index bee8682..dc93e02 100644 --- a/src/main/java/codes/thischwa/cf/model/BatchEntry.java +++ b/src/main/java/codes/thischwa/cf/model/BatchEntry.java @@ -5,11 +5,11 @@ import lombok.Data; import lombok.EqualsAndHashCode; /** - * Represents a batch entry containing different types of operations on record entities. + * Represents a batch entry containing different types of operations on getRecord entities. * *

A BatchEntry groups together collections of operations (patches, posts, puts, and deletes) * intended to be performed as part of a single batch process. Each operation corresponds to a specific - * type of action on DNS record entities. + * type of action on DNS getRecord entities. * *

    *
  • patches: A list of {@link RecordEntity} objects representing partial updates to existing records. diff --git a/src/main/java/codes/thischwa/cf/model/BatchResponse.java b/src/main/java/codes/thischwa/cf/model/BatchResponse.java index d66b892..9ba4a0c 100644 --- a/src/main/java/codes/thischwa/cf/model/BatchResponse.java +++ b/src/main/java/codes/thischwa/cf/model/BatchResponse.java @@ -5,7 +5,7 @@ package codes.thischwa.cf.model; * *

    This class is used for API responses where the primary result is a batch entry, * which includes collections of operations such as patches, posts, puts, and deletes - * performed on DNS record entities. + * performed on DNS getRecord entities. * *

    Extends {@code AbstractSingleResponse} with {@code BatchEntry} as the generic type, * ensuring that the response result is a batch of operations. diff --git a/src/main/java/codes/thischwa/cf/model/PagingRequest.java b/src/main/java/codes/thischwa/cf/model/PagingRequest.java index ac061a3..773d002 100644 --- a/src/main/java/codes/thischwa/cf/model/PagingRequest.java +++ b/src/main/java/codes/thischwa/cf/model/PagingRequest.java @@ -25,7 +25,7 @@ public class PagingRequest { * Default page size for retrieving all records in a single request. * Set to a very high value to effectively disable pagination when fetching all records. */ - private static final int DEFAULT_ALL_RECORDS_PAGE_SIZE = 5_000_000; + private static final int DEFAULT_ALL_RECORDS_PAGE_SIZE = 1000; private int page; private int perPage; @@ -77,7 +77,7 @@ public class PagingRequest { } private String queryString(boolean add) { - String qs = "page=" + page + "&perPage=" + perPage; + String qs = "page=" + page + "&per_page=" + perPage; return add ? "&" + qs : "?" + qs; } } diff --git a/src/main/java/codes/thischwa/cf/model/RecordEntity.java b/src/main/java/codes/thischwa/cf/model/RecordEntity.java index 7fb32d3..5f18daa 100644 --- a/src/main/java/codes/thischwa/cf/model/RecordEntity.java +++ b/src/main/java/codes/thischwa/cf/model/RecordEntity.java @@ -6,22 +6,22 @@ import lombok.EqualsAndHashCode; import org.jetbrains.annotations.Nullable; /** - * Represents a DNS record entity within a specific zone. + * Represents a DNS getRecord entity within a specific zone. * *

    Attributes defined in this class include: * *

      - *
    • DNS record type such as "A" or "CNAME". - *
    • Name of the DNS record. - *
    • Content of the DNS record, such as an IP address. - *
    • Flags indicating whether the record is proxiable or proxied. - *
    • TTL (Time-To-Live) for the DNS record. - *
    • A locked status to indicate the immutability of the record. + *
    • DNS getRecord type such as "A" or "CNAME". + *
    • Name of the DNS getRecord. + *
    • Content of the DNS getRecord, such as an IP address. + *
    • Flags indicating whether the getRecord is proxiable or proxied. + *
    • TTL (Time-To-Live) for the DNS getRecord. + *
    • A locked status to indicate the immutability of the getRecord. *
    • Zone-specific metadata including zone ID and name. *
    • Timestamps for creation and modification. *
    * - *

    Provides a static factory method {@code build} for creating a DNS record with specific + *

    Provides a static factory method {@code build} for creating a DNS getRecord with specific * attributes. */ @EqualsAndHashCode(callSuper = true) @@ -45,7 +45,7 @@ public class RecordEntity extends AbstractEntity { /** * Initializes a new instance of the RecordEntity class and invokes the parent constructor from - * the AbstractEntity class. The RecordEntity class represents a DNS record entity within a + * the AbstractEntity class. The RecordEntity class represents a DNS getRecord entity within a * specific zone, encapsulating attributes such as type, name, content, TTL, and other related * metadata. */ @@ -56,10 +56,10 @@ public class RecordEntity extends AbstractEntity { /** * Builds and returns a {@link RecordEntity} instance with the specified attributes. * - * @param name the name of the DNS record - * @param type the {@link RecordType} of the DNS record - * @param ttl the time-to-live (TTL) value for the DNS record - * @param content the content of the DNS record, typically an IP address + * @param name the name of the DNS getRecord + * @param type the {@link RecordType} of the DNS getRecord + * @param ttl the time-to-live (TTL) value for the DNS getRecord + * @param content the content of the DNS getRecord, typically an IP address * @return a {@link RecordEntity} populated with the provided attributes */ public static RecordEntity build(String name, RecordType type, Integer ttl, String content) { @@ -74,8 +74,8 @@ public class RecordEntity extends AbstractEntity { /** * Builds and returns a {@link RecordEntity} instance with the specified ID and content. * - * @param id the unique identifier for the DNS record - * @param content the content of the DNS record, typically an IP address or other record data + * @param id the unique identifier for the DNS getRecord + * @param content the content of the DNS getRecord, typically an IP address or other getRecord data * @return a {@link RecordEntity} populated with the provided ID and content */ public static RecordEntity build(String id, String content) { @@ -88,11 +88,11 @@ public class RecordEntity extends AbstractEntity { /** * Builds and returns a {@link RecordEntity} instance with the specified attributes. * - * @param id the unique identifier for the DNS record - * @param name the name of the DNS record - * @param type the type of the DNS record, represented as a string (e.g., "A", "CNAME") - * @param ttl the time-to-live (TTL) value for the DNS record - * @param content the content of the DNS record, typically an IP address or other record data + * @param id the unique identifier for the DNS getRecord + * @param name the name of the DNS getRecord + * @param type the type of the DNS getRecord, represented as a string (e.g., "A", "CNAME") + * @param ttl the time-to-live (TTL) value for the DNS getRecord + * @param content the content of the DNS getRecord, typically an IP address or other getRecord data * @return a {@link RecordEntity} populated with the provided attributes * @throws IllegalArgumentException if the type string is not a valid RecordType */ @@ -101,7 +101,7 @@ public class RecordEntity extends AbstractEntity { try { recordType = RecordType.valueOf(type); } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid record type: " + type + ". Must be one of: " + throw new IllegalArgumentException("Invalid getRecord type: " + type + ". Must be one of: " + java.util.Arrays.toString(RecordType.values()), e); } RecordEntity rec = new RecordEntity(); @@ -114,11 +114,11 @@ public class RecordEntity extends AbstractEntity { } /** - * Retrieves the short name (subdomain) of the DNS record. + * Retrieves the short name (subdomain) of the DNS getRecord. * If the name contains a dot ('.'), only the substring before the first dot is returned. * This is useful for getting the subdomain part of a fully qualified domain name. * - * @return the short name of the DNS record (substring before the first dot), + * @return the short name of the DNS getRecord (substring before the first dot), * or the full name if no dot is present */ public String getSld() { diff --git a/src/main/java/codes/thischwa/cf/model/RecordMultipleResponse.java b/src/main/java/codes/thischwa/cf/model/RecordMultipleResponse.java index 466e06d..5ea8672 100644 --- a/src/main/java/codes/thischwa/cf/model/RecordMultipleResponse.java +++ b/src/main/java/codes/thischwa/cf/model/RecordMultipleResponse.java @@ -9,7 +9,7 @@ public class RecordMultipleResponse extends AbstractMultipleResponseThis class represents a response containing multiple DNS record entities from the + *

    This class represents a response containing multiple DNS getRecord entities from the * Cloudflare API. It inherits functionality from AbstractMultipleResponse to handle multiple * records of type RecordEntity. */ diff --git a/src/main/java/codes/thischwa/cf/model/RecordSingleResponse.java b/src/main/java/codes/thischwa/cf/model/RecordSingleResponse.java index 5b1b9a0..48d0a21 100644 --- a/src/main/java/codes/thischwa/cf/model/RecordSingleResponse.java +++ b/src/main/java/codes/thischwa/cf/model/RecordSingleResponse.java @@ -11,7 +11,7 @@ public class RecordSingleResponse extends AbstractSingleResponse { * *

    This constructor initializes the RecordSingleResponse object by invoking the superclass * constructor. The RecordSingleResponse represents a specific API response structure that - * encapsulates a single DNS record entity, providing mechanisms to interact with such data in the + * encapsulates a single DNS getRecord entity, providing mechanisms to interact with such data in the * context of the Cloudflare API. */ public RecordSingleResponse() { diff --git a/src/main/java/codes/thischwa/cf/model/RecordType.java b/src/main/java/codes/thischwa/cf/model/RecordType.java index bff7a80..5d79f53 100644 --- a/src/main/java/codes/thischwa/cf/model/RecordType.java +++ b/src/main/java/codes/thischwa/cf/model/RecordType.java @@ -3,86 +3,86 @@ package codes.thischwa.cf.model; import lombok.Getter; /** - * Enum representing various DNS record types. + * Enum representing various DNS getRecord types. * - *

    Each constant in this enum corresponds to a specific DNS record type, such as "A", "AAAA", - * "CNAME", or "TXT". This enum provides a means to standardize the representation of these record + *

    Each constant in this enum corresponds to a specific DNS getRecord type, such as "A", "AAAA", + * "CNAME", or "TXT". This enum provides a means to standardize the representation of these getRecord * types throughout the application while allowing easy retrieval of their string representation. */ @Getter public enum RecordType { /** - * Represents the DNS A record type. + * Represents the DNS A getRecord type. * - *

    The "A" record type is used to map a domain name to an IPv4 address. + *

    The "A" getRecord type is used to map a domain name to an IPv4 address. */ A("A"), /** - * Represents the DNS AAAA record type. + * Represents the DNS AAAA getRecord type. * - *

    The "AAAA" record type maps a domain name to an IPv6 address. + *

    The "AAAA" getRecord type maps a domain name to an IPv6 address. */ AAAA("AAAA"), /** - * Represents the DNS CAA (Certificate Authority Authorization) record type. + * Represents the DNS CAA (Certificate Authority Authorization) getRecord type. * - *

    The "CAA" record type is used to specify which certificate authorities (CAs) are allowed to + *

    The "CAA" getRecord type is used to specify which certificate authorities (CAs) are allowed to * issue SSL/TLS certificates for a domain. */ CAA("CAA"), /** - * Represents the DNS CERT record type. + * Represents the DNS CERT getRecord type. * - *

    The "CERT" record type is used to store certificates and related certificate revocation + *

    The "CERT" getRecord type is used to store certificates and related certificate revocation * lists or certificate authority data in DNS zones. */ CERT("CERT"), /** - * Represents the DNS CNAME (Canonical Name) record type. + * Represents the DNS CNAME (Canonical Name) getRecord type. * - *

    The "CNAME" record type is used to alias one domain name to another. + *

    The "CNAME" getRecord type is used to alias one domain name to another. */ CNAME("CNAME"), /** - * Represents the DNSKEY record type. + * Represents the DNSKEY getRecord type. * - *

    The "DNSKEY" record type is used for storing public keys in DNS, as part of the DNS Security + *

    The "DNSKEY" getRecord type is used for storing public keys in DNS, as part of the DNS Security * Extensions (DNSSEC). It helps in verifying the authenticity of DNS responses through digital * signatures. */ DNSKEY("DNSKEY"), /** - * Represents the DNS DS (Delegation Signer) record type. + * Represents the DNS DS (Delegation Signer) getRecord type. * - *

    The "DS" record type is used in the DNSSEC (Domain Name System Security Extensions) - * protocol. It contains a hash of a DNSKEY record which is utilized in establishing a chain of + *

    The "DS" getRecord type is used in the DNSSEC (Domain Name System Security Extensions) + * protocol. It contains a hash of a DNSKEY getRecord which is utilized in establishing a chain of * trust from a parent zone to a child zone. */ DS("DS"), /** - * Represents the DNS HTTPS (HTTP Service) record type. + * Represents the DNS HTTPS (HTTP Service) getRecord type. * - *

    The "HTTPS" record type is used to specify information about the HTTP/3 and related services + *

    The "HTTPS" getRecord type is used to specify information about the HTTP/3 and related services * offered by a domain. */ HTTPS("HTTPS"), /** - * Represents the DNS LOC (Location) record type. + * Represents the DNS LOC (Location) getRecord type. * - *

    The "LOC" record type is used to store geographical location information for a domain, + *

    The "LOC" getRecord type is used to store geographical location information for a domain, * including latitude, longitude, altitude, and other details. It enables associating domains with * physical locations. */ LOC("LOC"), /** - * Represents the DNS MX (Mail Exchange) record type. + * Represents the DNS MX (Mail Exchange) getRecord type. * - *

    The "MX" record type is used to specify the mail servers responsible for receiving email + *

    The "MX" getRecord type is used to specify the mail servers responsible for receiving email * messages on behalf of a domain. */ MX("MX"), /** - * Represents the NAPTR record type for DNS configurations. + * Represents the NAPTR getRecord type for DNS configurations. * *

    This constant is used to specify NAPTR (Naming Authority Pointer) DNS records, which allow * for service discovery through flexible DNS-based mechanisms. NAPTR records are commonly used in @@ -99,7 +99,7 @@ public enum RecordType { */ NS("NS"), /** - * Represents the "OPENPGPKEY" DNS record type. + * Represents the "OPENPGPKEY" DNS getRecord type. * *

    This constant is primarily used to identify DNS records of the type "OPENPGPKEY". It may be * utilized within the system for operations such as creating, retrieving, updating, or managing @@ -107,17 +107,17 @@ public enum RecordType { */ OPENPGPKEY("OPENPGPKEY"), /** - * Represents a DNS record type. + * Represents a DNS getRecord type. * - *

    The `PTR` value specifically refers to a "pointer record" in the DNS system, which is + *

    The `PTR` value specifically refers to a "pointer getRecord" in the DNS system, which is * typically used for reverse DNS lookups. */ PTR("PTR"), /** - * Represents the SMIMEA DNS record type. + * Represents the SMIMEA DNS getRecord type. * - *

    The SMIMEA resource record is used to associate a user's certificate information for email - * message signing or encryption. This type of DNS record is part of the DNS-based Authentication + *

    The SMIMEA resource getRecord is used to associate a user's certificate information for email + * message signing or encryption. This type of DNS getRecord is part of the DNS-based Authentication * of Named Entities (DANE) protocol. * *

    SMIMEA records provide a mechanism for utilizing certificates in email communication @@ -134,41 +134,41 @@ public enum RecordType { */ SMIMEA("SMIMEA"), /** - * Represents a service record (SRV) type in the DNS configuration model. + * Represents a service getRecord (SRV) type in the DNS configuration model. * - *

    This constant may be used to identify and work with SRV record types in various DNS-related + *

    This constant may be used to identify and work with SRV getRecord types in various DNS-related * operations or integrations. */ SRV("SRV"), /** - * Represents the DNS record type "SSHFP" (SSH Fingerprint), used in DNS to store cryptographic + * Represents the DNS getRecord type "SSHFP" (SSH Fingerprint), used in DNS to store cryptographic * fingerprints associated with SSH host keys. * - *

    This DNS record type provides a mechanism for verifying the authenticity of an SSH server + *

    This DNS getRecord type provides a mechanism for verifying the authenticity of an SSH server * before initiating a connection, enhancing the security of SSH communications. */ SSHFP("SSHFP"), /** - * Represents the Service Binding (SVCB) DNS record type. + * Represents the Service Binding (SVCB) DNS getRecord type. * - *

    The SVCB record is a DNS resource record used to indicate alternative endpoints or specific + *

    The SVCB getRecord is a DNS resource getRecord used to indicate alternative endpoints or specific * configuration details for services. It is commonly applied in service discovery and * protocol-specific configurations. */ SVCB("SVCB"), /** - * Represents a constant for the DNS-based Authentication of Named Entities (DANE) TLSA record + * Represents a constant for the DNS-based Authentication of Named Entities (DANE) TLSA getRecord * type. * - *

    The TLSA record is used to associate a TLS server certificate or public key with the domain + *

    The TLSA getRecord is used to associate a TLS server certificate or public key with the domain * name (e.g., via DNSSEC). It enables cryptographically secured connections by attaching * certificate and key constraints to the specific domain. */ TLSA("TLSA"), /** - * Represents the TXT DNS record type. + * Represents the TXT DNS getRecord type. * - *

    The TXT DNS record type is commonly used to store text-based information for various + *

    The TXT DNS getRecord type is commonly used to store text-based information for various * verification and configuration purposes, such as domain ownership verification or email * authentication protocols (e.g., SPF, DKIM). */ diff --git a/src/test/java/codes/thischwa/cf/CfBasicHttpClientTest.java b/src/test/java/codes/thischwa/cf/CfBasicHttpClientTest.java new file mode 100644 index 0000000..0c0e6cd --- /dev/null +++ b/src/test/java/codes/thischwa/cf/CfBasicHttpClientTest.java @@ -0,0 +1,157 @@ +package codes.thischwa.cf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import codes.thischwa.cf.model.RecordEntity; +import codes.thischwa.cf.model.RecordSingleResponse; +import codes.thischwa.cf.model.RecordType; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for CfBasicHttpClient using mocked HTTP components. + * Tests HTTP client functionality without requiring actual network calls. + */ +class CfBasicHttpClientTest { + + /** + * Test implementation of CfBasicHttpClient for testing purposes. + */ + private static class TestCfBasicHttpClient extends CfBasicHttpClient { + + TestCfBasicHttpClient(String baseUrl, CfDnsClientBuilder.CfAuth auth) { + super(baseUrl, auth); + } + + // Expose protected methods for testing + public T testGetRequest(String endpoint, Class responseType) + throws CloudflareApiException { + return getRequest(endpoint, responseType); + } + + public T testPostRequest(String endpoint, Object payload, Class responseType) + throws CloudflareApiException { + return postRequest(endpoint, payload, responseType); + } + + public T testPutRequest(String endpoint, Object payload, Class responseType) + throws CloudflareApiException { + return putRequest(endpoint, payload, responseType); + } + + public T testPatchRequest(String endpoint, Object payload, Class responseType) + throws CloudflareApiException { + return patchRequest(endpoint, payload, responseType); + } + + public T testDeleteRequest(String endpoint, Class responseType) + throws CloudflareApiException { + return deleteRequest(endpoint, responseType); + } + } + + @Test + void testConstructor_WithApiToken() { + CfDnsClientBuilder.ApiTokenAuth auth = new CfDnsClientBuilder.ApiTokenAuth("test-token"); + TestCfBasicHttpClient client = new TestCfBasicHttpClient("https://api.cloudflare.com", auth); + assertNotNull(client); + } + + @Test + void testConstructor_WithEmailKey() { + CfDnsClientBuilder.EmailKeyAuth auth = new CfDnsClientBuilder.EmailKeyAuth("test@example.com", "test-key"); + TestCfBasicHttpClient client = new TestCfBasicHttpClient("https://api.cloudflare.com", auth); + assertNotNull(client); + } + + @Test + void testApiException_unknowEndpoint() { + CfDnsClientBuilder.ApiTokenAuth auth = new CfDnsClientBuilder.ApiTokenAuth("test-token"); + TestCfBasicHttpClient client = new TestCfBasicHttpClient("https://api.cloudflare.com", auth); + + // Invalid JSON will cause a parsing error + CloudflareApiException exception = assertThrows(CloudflareApiException.class, () -> { + client.testGetRequest("/invalid-endpoint", RecordSingleResponse.class); + }); + + assertNotNull(exception); + assertTrue(exception.getMessage().contains("Unexpected error")); + Throwable cause = exception.getCause(); + assertInstanceOf(CloudflareApiException.class, cause); + assertTrue(cause.getMessage().contains("No route for that URI"), "Expected error message: No route for that URI, but it was: " + cause.getMessage()); + } + + @Test + void testResultWrapper() throws Exception { + // Test the private ResultWrapper record indirectly through client behavior + CfDnsClientBuilder.ApiTokenAuth auth = new CfDnsClientBuilder.ApiTokenAuth("test-token"); + TestCfBasicHttpClient client = new TestCfBasicHttpClient("https://api.cloudflare.com", auth); + + // Any request will create and use a ResultWrapper internally + assertThrows(CloudflareApiException.class, () -> { + client.testGetRequest("/test", RecordSingleResponse.class); + }); + } + + @Test + void testAuthApplicationHeader_ApiToken() { + CfDnsClientBuilder.ApiTokenAuth auth = new CfDnsClientBuilder.ApiTokenAuth("my-token"); + HttpGet request = new HttpGet("https://api.cloudflare.com/test"); + + auth.applyAuth(request); + + assertTrue(request.containsHeader("Authorization")); + assertEquals("Bearer my-token", request.getFirstHeader("Authorization").getValue()); + } + + @Test + void testAuthApplicationHeader_EmailKey() { + CfDnsClientBuilder.EmailKeyAuth auth = new CfDnsClientBuilder.EmailKeyAuth("test@example.com", "my-key"); + HttpGet request = new HttpGet("https://api.cloudflare.com/test"); + + auth.applyAuth(request); + + assertTrue(request.containsHeader("X-Auth-Email")); + assertTrue(request.containsHeader("X-Auth-Key")); + assertEquals("test@example.com", request.getFirstHeader("X-Auth-Email").getValue()); + assertEquals("my-key", request.getFirstHeader("X-Auth-Key").getValue()); + } + + @Test + void testBaseUrlConstruction() { + CfDnsClientBuilder.ApiTokenAuth auth = new CfDnsClientBuilder.ApiTokenAuth("test-token"); + + // Test default base URL + TestCfBasicHttpClient client = new TestCfBasicHttpClient("https://api.cloudflare.com", auth); + assertNotNull(client); + } + + @Test + void testObjectMapperInitialization() { + // ObjectMapper is initialized in constructor via JsonConf + CfDnsClientBuilder.ApiTokenAuth auth = new CfDnsClientBuilder.ApiTokenAuth("test-token"); + TestCfBasicHttpClient client = new TestCfBasicHttpClient("https://api.cloudflare.com", auth); + + // If ObjectMapper wasn't initialized, any request would fail with NullPointerException + assertThrows(CloudflareApiException.class, () -> { + client.testGetRequest("/test", RecordSingleResponse.class); + }); + } + + @Test + void testRequestPayloadSerialization() { + CfDnsClientBuilder.ApiTokenAuth auth = new CfDnsClientBuilder.ApiTokenAuth("test-token"); + TestCfBasicHttpClient client = new TestCfBasicHttpClient("https://api.cloudflare.com", auth); + + RecordEntity record = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + + // The payload serialization happens inside postRequest + assertThrows(CloudflareApiException.class, () -> { + client.testPostRequest("/test", record, RecordSingleResponse.class); + }); + } +} diff --git a/src/test/java/codes/thischwa/cf/CfClientPenTest.java b/src/test/java/codes/thischwa/cf/CfClientPenTest.java index 82f0186..a2bccb5 100644 --- a/src/test/java/codes/thischwa/cf/CfClientPenTest.java +++ b/src/test/java/codes/thischwa/cf/CfClientPenTest.java @@ -66,7 +66,7 @@ public class CfClientPenTest { } @Test - @DisplayName("Invalid record content and TTL boundaries must be rejected by API") + @DisplayName("Invalid getRecord content and TTL boundaries must be rejected by API") void testInvalidRecordCreateInputsRejected() throws Exception { CfDnsClient client = newClient(); ZoneEntity zone = client.zoneGet(ZONE_STR); @@ -81,11 +81,11 @@ public class CfClientPenTest { } try { - // A record with invalid IPv4 + // A getRecord with invalid IPv4 assertThrows(CloudflareApiException.class, () -> client.recordCreate(zone, fqdn, 60, RecordType.A, "999.999.999.999")); - // AAAA record with non-IP content + // AAAA getRecord with non-IP content assertThrows(CloudflareApiException.class, () -> client.recordCreate(zone, fqdn, 60, RecordType.AAAA, "not-an-ipv6")); diff --git a/src/test/java/codes/thischwa/cf/CfClientTest.java b/src/test/java/codes/thischwa/cf/CfClientTest.java index c1c7305..dce178a 100644 --- a/src/test/java/codes/thischwa/cf/CfClientTest.java +++ b/src/test/java/codes/thischwa/cf/CfClientTest.java @@ -9,14 +9,18 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; import codes.thischwa.cf.model.BatchEntry; +import codes.thischwa.cf.model.PagingRequest; import codes.thischwa.cf.model.RecordEntity; import codes.thischwa.cf.model.RecordType; import codes.thischwa.cf.model.ZoneEntity; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -91,6 +95,19 @@ public class CfClientTest { } } + @Test + void testZoneList() throws CloudflareApiException { + List zones = client.zoneList(); + assertNotNull(zones); + assertFalse(zones.isEmpty()); + assertEquals(ZONE_STR, zones.get(0).getName()); + + zones = client.zoneList(PagingRequest.of(1, 100)); + assertNotNull(zones); + assertFalse(zones.isEmpty()); + assertEquals(ZONE_STR, zones.get(0).getName()); + } + @Test void testZoneListAnlFailedSldList() throws Exception { List zList = client.zoneList(); @@ -127,7 +144,7 @@ public class CfClientTest { // ensure clean state client.recordDeleteTypeIfExists(z, randomSld, RecordType.A, RecordType.AAAA); - // create A record using recordCreate with full domain + // create A getRecord using recordCreate with full domain createdRe1 = client.recordCreate(z, RecordEntity.build(domain, RecordType.A, TTL, "130.0.0.3")); assertNotNull(createdRe1.getId()); @@ -146,7 +163,7 @@ public class CfClientTest { r = aRecords.get(0); assertEquals("130.0.0.3", r.getContent()); - // create AAAA record using recordCreateSld + // create AAAA getRecord using recordCreateSld createdRe2 = client.recordCreateSld(z, randomSld, TTL, RecordType.AAAA, "2a0a:4cc0:c0:2e4::1"); List aaaaRecords = client.recordList(z, randomSld, RecordType.AAAA); @@ -166,7 +183,7 @@ public class CfClientTest { } else if (Objects.equals(re.getType(), RecordType.AAAA.getType())) { assertEquals("2a0a:4cc0:c0:2e4::1", re.getContent()); } else { - fail(String.format("Unexpected record type: %s", re.getType())); + fail(String.format("Unexpected getRecord type: %s", re.getType())); } } @@ -181,7 +198,6 @@ public class CfClientTest { // test recordList with types without SLD List aList = client.recordList(z, RecordType.A); assertFalse(aList.isEmpty()); - assertTrue(aList.size() >= 1); assertTrue(aList.stream().anyMatch(re -> re.getId().equals(createdRe1.getId()))); assertTrue(aList.stream().noneMatch(re -> re.getId().equals(createdRe2.getId()))); assertTrue(aList.stream().allMatch(re -> re.getType().equals(RecordType.A.getType()))); @@ -191,7 +207,7 @@ public class CfClientTest { assertFalse(fluentList.isEmpty()); assertTrue(fluentList.stream().anyMatch(re -> re.getId().equals(createdRe1.getId()))); - // update AAAA record + // update AAAA getRecord createdRe2.setContent("2a0a:4cc0:c0:2e4::2"); client.recordUpdate(z, createdRe2); aaaaRecords = client.recordList(z, randomSld, RecordType.AAAA); @@ -199,18 +215,18 @@ public class CfClientTest { r = aaaaRecords.get(0); assertEquals("2a0a:4cc0:c0:2e4::2", r.getContent()); - // verify A record still intact + // verify A getRecord still intact aRecords = client.recordList(z, randomSld, RecordType.A); assertEquals(1, aRecords.size()); r = aRecords.get(0); assertEquals("130.0.0.3", r.getContent()); - // delete AAAA record and verify it's gone + // delete AAAA getRecord and verify it's gone assertTrue(client.recordDelete(z, createdRe2)); assertThrows(CloudflareNotFoundException.class, () -> client.recordList(z, randomSld, RecordType.AAAA)); - // delete A record using helper and verify it's gone + // delete A getRecord using helper and verify it's gone client.recordDeleteTypeIfExists(z, randomSld, RecordType.A); assertThrows(CloudflareNotFoundException.class, () -> client.recordList(z, randomSld, RecordType.A)); @@ -226,7 +242,7 @@ public class CfClientTest { void testRecordEntityInvalidType() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> RecordEntity.build("id123", "example.com", "INVALID_TYPE", 60, "192.168.1.1")); - assertTrue(exception.getMessage().contains("Invalid record type: INVALID_TYPE")); + assertTrue(exception.getMessage().contains("Invalid getRecord type: INVALID_TYPE")); assertTrue(exception.getMessage().contains("Must be one of:")); } @@ -373,7 +389,7 @@ public class CfClientTest { try { // Test fluent create RecordEntity created = client.zone(ZONE_STR) - .record(fluentSld) + .getRecord(fluentSld) .create(RecordType.A, "192.168.100.1", TTL); assertNotNull(created.getId()); @@ -382,7 +398,7 @@ public class CfClientTest { // Test fluent get List records = client.zone(ZONE_STR) - .record(fluentSld, RecordType.A) + .getRecord(fluentSld, RecordType.A) .get(); assertEquals(1, records.size()); @@ -390,18 +406,18 @@ public class CfClientTest { // Test fluent update RecordEntity updated = client.zone(ZONE_STR) - .record(fluentSld, RecordType.A) + .getRecord(fluentSld, RecordType.A) .update("192.168.100.2"); assertEquals("192.168.100.2", updated.getContent()); // Test fluent delete client.zone(ZONE_STR) - .record(fluentSld) + .getRecord(fluentSld) .delete(RecordType.A); assertThrows(CloudflareNotFoundException.class, - () -> client.zone(ZONE_STR).record(fluentSld, RecordType.A).get()); + () -> client.zone(ZONE_STR).getRecord(fluentSld, RecordType.A).get()); } finally { try { @@ -425,7 +441,7 @@ public class CfClientTest { assertNotNull(groupedRecords, "Resulting map should not be null."); assertEquals(2, groupedRecords.size(), "The grouping should result in 2 FQDN keys."); assertEquals(2, groupedRecords.get("example.com.").size(), "The key 'example.com.' should have 2 records."); - assertEquals(1, groupedRecords.get("sub.example.com.").size(), "The key 'sub.example.com.' should have 1 record."); + assertEquals(1, groupedRecords.get("sub.example.com.").size(), "The key 'sub.example.com.' should have 1 getRecord."); } @Test @@ -445,4 +461,68 @@ public class CfClientTest { } + @Test + void testPaging() throws Exception { + ZoneEntity zone = client.zoneGet(ZONE_STR); + String pagingSld = "paging-" + System.currentTimeMillis(); + + try { + int existingCount = 0; + try { + List allRecords = client.recordList(zone); + existingCount = allRecords.size(); + } catch (CloudflareApiException e) { + // ignore + } + + // Calculate how many records we need to create to reach at least 12 total A records + // (to test paging with pageSize 5: page 1 = 5, page 2 = 5, page 3 = 2+) + int targetCount = 12; + int recordsToCreate = Math.max(0, targetCount - existingCount); + + // Create additional A records if needed + List createdRecords = new ArrayList<>(); + for (int i = 1; i <= recordsToCreate; i++) { + RecordEntity record = RecordEntity.build(pagingSld, RecordType.A, TTL, "127.0.0." + i); + RecordEntity created = client.recordCreate(zone, record); + createdRecords.add(created); + assertNotNull(created.getId()); + } + + // Test paging with page size of 5 + PagingRequest page1Request = PagingRequest.of(1, 5); + List page1Records = client.recordList(zone, page1Request); + assertEquals(5, page1Records.size(), "First page should contain 5 records"); + + // 2nd page should also contain 5 records (if we have at least 12 total) + PagingRequest page2Request = PagingRequest.of(2, 5); + List page2Records = client.recordList(zone, page2Request); + assertEquals(5, page2Records.size(), "Second page should contain at least 5 records"); + + // 3rd page should contain 2 records + PagingRequest page3Request = PagingRequest.of(3, 5); + List page3Records = client.recordList(zone, page3Request); + assertEquals(2, page3Records.size(), "Third page should contain 2 records"); + + // Verify no overlap between pages + List page1Ids = page1Records.stream().map(RecordEntity::getId).toList(); + List page2Ids = page2Records.stream().map(RecordEntity::getId).toList(); + List page3Ids = page3Records.stream().map(RecordEntity::getId).toList(); + Set generatedRecordIds = new HashSet<>(page1Ids); + generatedRecordIds.addAll(page2Ids); + generatedRecordIds.addAll(page3Ids); + assertEquals(createdRecords.size(), generatedRecordIds.size()); + + // Verify our created records are in the zone + List allRecords = client.recordList(zone); + Set allRecordIds = allRecords.stream().map(RecordEntity::getId).collect(Collectors.toSet()); + assertEquals(createdRecords.size(), allRecordIds.size()); + assertTrue(allRecordIds.containsAll(generatedRecordIds)); + } finally { + try { + client.recordDeleteTypeIfExists(zone, pagingSld, RecordType.A); + } catch (Exception e) { /* ignore */ } + } + } + } diff --git a/src/test/java/codes/thischwa/cf/CfDnsClientBuilderTest.java b/src/test/java/codes/thischwa/cf/CfDnsClientBuilderTest.java new file mode 100644 index 0000000..aefeeab --- /dev/null +++ b/src/test/java/codes/thischwa/cf/CfDnsClientBuilderTest.java @@ -0,0 +1,150 @@ +package codes.thischwa.cf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for CfDnsClientBuilder and its authentication classes. + */ +class CfDnsClientBuilderTest { + + @Test + void testBuildWithApiToken() { + CfDnsClient client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .build(); + + assertNotNull(client); + } + + @Test + void testBuildWithEmailKey() { + CfDnsClient client = new CfDnsClientBuilder() + .withEmailKeyAuth("test@example.com", "test-key") + .build(); + + assertNotNull(client); + } + + @Test + void testBuildWithCustomBaseUrl() { + CfDnsClient client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .withBaseUrl("https://custom-api.example.com") + .build(); + + assertNotNull(client); + } + + @Test + void testBuildWithDefaultBaseUrl() { + CfDnsClient client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .build(); + + assertNotNull(client); + } + + @Test + void testBuildWithEmptyResultThrowsException() { + CfDnsClient client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .withEmptyResultThrowsException(true) + .build(); + + assertNotNull(client); + } + + @Test + void testBuildWithEmptyResultDoesNotThrowException() { + CfDnsClient client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .withEmptyResultThrowsException(false) + .build(); + + assertNotNull(client); + } + + @Test + void testBuilderMethodChaining() { + CfDnsClient client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .withBaseUrl("https://custom-api.example.com") + .withEmptyResultThrowsException(true) + .build(); + + assertNotNull(client); + } + + @Test + void testApiTokenAuth_BlankToken() { + assertThrows(IllegalArgumentException.class, () -> new CfDnsClientBuilder.ApiTokenAuth(" ")); + } + + @Test + void testEmailKeyAuth_ValidCredentials() { + CfDnsClientBuilder.EmailKeyAuth auth = new CfDnsClientBuilder.EmailKeyAuth( + "test@example.com", + "valid-key" + ); + assertNotNull(auth); + } + + @Test + void testEmailKeyAuth_BlankEmail() { + assertThrows(IllegalArgumentException.class, () -> new CfDnsClientBuilder.EmailKeyAuth(" ", "valid-key")); + } + + @Test + void testEmailKeyAuth_BlankKey() { + assertThrows(IllegalArgumentException.class, () -> new CfDnsClientBuilder.EmailKeyAuth("test@example.com", " ")); + } + + @Test + void testEmailKeyAuth_BothBlank() { + assertThrows(IllegalArgumentException.class, () -> new CfDnsClientBuilder.EmailKeyAuth(" ", " ")); + } + + @Test + void testDefaultBaseUrl() { + assertEquals("https://api.cloudflare.com/client/v4", CfDnsClientBuilder.DEFAULT_BASEURL); + } + + @Test + void testBuilderWithMultipleConfigurations() { + // Test switching auth methods in the same builder (last one wins) + CfDnsClient client = new CfDnsClientBuilder() + .withEmailKeyAuth("test@example.com", "old-key") + .withApiTokenAuth("new-token") // This should override the email/key auth + .build(); + + assertNotNull(client); + } + + @Test + void testBuilderWithMultipleBaseUrls() { + // Test setting base URL multiple times (last one wins) + CfDnsClient client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .withBaseUrl("https://old-api.example.com") + .withBaseUrl("https://new-api.example.com") // This should override + .build(); + + assertNotNull(client); + } + + @Test + void testEmptyResultThrowsExceptionToggle() { + // Test toggling the flag multiple times (last one wins) + CfDnsClient client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .withEmptyResultThrowsException(true) + .withEmptyResultThrowsException(false) // This should override + .build(); + + assertNotNull(client); + } +} diff --git a/src/test/java/codes/thischwa/cf/CfDnsClientMockTest.java b/src/test/java/codes/thischwa/cf/CfDnsClientMockTest.java new file mode 100644 index 0000000..bc5b7f9 --- /dev/null +++ b/src/test/java/codes/thischwa/cf/CfDnsClientMockTest.java @@ -0,0 +1,399 @@ +package codes.thischwa.cf; + +import codes.thischwa.cf.fluent.ZoneOperations; +import codes.thischwa.cf.model.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for CfDnsClient using mocked HTTP client responses. + * Tests all public methods without requiring actual Cloudflare API access. + */ +@ExtendWith(MockitoExtension.class) +class CfDnsClientMockTest { + + private CfDnsClient client; + + @Mock + private CfBasicHttpClient mockHttpClient; + + private static final String TEST_ZONE_ID = "zone123"; + private static final String TEST_ZONE_NAME = "example.com"; + private static final String TEST_RECORD_ID = "rec123"; + + private ZoneEntity createTestZone() { + ZoneEntity zone = new ZoneEntity(); + zone.setId(TEST_ZONE_ID); + zone.setName(TEST_ZONE_NAME); + return zone; + } + + private ResponseResultInfo createSuccessResultInfo() { + ResponseResultInfo resultInfo = new ResponseResultInfo(); + resultInfo.setSuccess(true); + return resultInfo; + } + + private BatchResponse createBatchResponse() throws Exception { + // Use reflection to access package-private constructor + java.lang.reflect.Constructor constructor = BatchResponse.class.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } + + private ResultInfo createResultInfo(int count) { + return new ResultInfo(count); + } + + @BeforeEach + void setUp() throws Exception { + // Create a real client with API token auth + client = new CfDnsClientBuilder() + .withApiTokenAuth("test-token") + .build(); + + // Replace the internal HTTP client with our mock using reflection + // Note: This is a workaround since CfBasicHttpClient methods are package-private + Field responseValidatorField = CfDnsClient.class.getDeclaredField("responseValidator"); + responseValidatorField.setAccessible(true); + responseValidatorField.set(client, new ResponseValidator(false)); + } + + @Test + void testGroupRecordsByFqdn() { + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + RecordEntity rec2 = RecordEntity.build("test.example.com", RecordType.AAAA, 300, "::1"); + RecordEntity rec3 = RecordEntity.build("www.example.com", RecordType.A, 300, "1.2.3.5"); + + List records = List.of(rec1, rec2, rec3); + Map> grouped = CfDnsClient.groupRecordsByFqdn(records); + + assertEquals(2, grouped.size()); + assertEquals(2, grouped.get("test.example.com").size()); + assertEquals(1, grouped.get("www.example.com").size()); + } + + @Test + void testGroupRecordsByFqdn_NullInput() { + Map> grouped = CfDnsClient.groupRecordsByFqdn(null); + assertNotNull(grouped); + assertTrue(grouped.isEmpty()); + } + + @Test + void testZoneList() throws Exception { + // Create mock zone response + ZoneMultipleResponse mockResponse = new ZoneMultipleResponse(); + ZoneEntity zone1 = new ZoneEntity(); + zone1.setId("zone1"); + zone1.setName("example.com"); + ZoneEntity zone2 = new ZoneEntity(); + zone2.setId("zone2"); + zone2.setName("test.com"); + mockResponse.setResult(List.of(zone1, zone2)); + ResponseResultInfo resultInfo = new ResponseResultInfo(); + resultInfo.setSuccess(true); + mockResponse.setResponseResultInfo(resultInfo); + + // Mock the HTTP client via spy + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).getRequest(anyString(), eq(ZoneMultipleResponse.class)); + + List zones = spyClient.zoneList(); + + assertNotNull(zones); + assertEquals(2, zones.size()); + assertEquals("example.com", zones.get(0).getName()); + assertEquals("test.com", zones.get(1).getName()); + } + + @Test + void testZoneGet() throws Exception { + // Create mock zone response + ZoneMultipleResponse mockResponse = new ZoneMultipleResponse(); + ZoneEntity zone = new ZoneEntity(); + zone.setId(TEST_ZONE_ID); + zone.setName(TEST_ZONE_NAME); + mockResponse.setResult(List.of(zone)); + ResponseResultInfo resultInfo = new ResponseResultInfo(); + resultInfo.setSuccess(true); + mockResponse.setResponseResultInfo(resultInfo); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).getRequest(anyString(), eq(ZoneMultipleResponse.class)); + + ZoneEntity result = spyClient.zoneGet(TEST_ZONE_NAME); + + assertNotNull(result); + assertEquals(TEST_ZONE_ID, result.getId()); + assertEquals(TEST_ZONE_NAME, result.getName()); + } + + @Test + void testZoneOperations() throws Exception { + // Create mock zone response + ZoneMultipleResponse mockResponse = new ZoneMultipleResponse(); + ZoneEntity zone = new ZoneEntity(); + zone.setId(TEST_ZONE_ID); + zone.setName(TEST_ZONE_NAME); + mockResponse.setResult(List.of(zone)); + ResponseResultInfo resultInfo = new ResponseResultInfo(); + resultInfo.setSuccess(true); + mockResponse.setResponseResultInfo(resultInfo); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).getRequest(anyString(), eq(ZoneMultipleResponse.class)); + + ZoneOperations ops = spyClient.zone(TEST_ZONE_NAME); + + assertNotNull(ops); + } + + @Test + void testRecordList_Zone() throws Exception { + ZoneEntity zone = new ZoneEntity(); + zone.setId(TEST_ZONE_ID); + zone.setName(TEST_ZONE_NAME); + + RecordMultipleResponse mockResponse = new RecordMultipleResponse(); + mockResponse.setResultInfo(createResultInfo(1)); + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + RecordEntity rec2 = RecordEntity.build("www.example.com", RecordType.A, 300, "1.2.3.5"); + mockResponse.setResult(List.of(rec1, rec2)); + ResponseResultInfo resultInfo = new ResponseResultInfo(); + resultInfo.setSuccess(true); + mockResponse.setResponseResultInfo(resultInfo); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).getRequest(anyString(), eq(RecordMultipleResponse.class)); + + List records = spyClient.recordList(zone); + + assertNotNull(records); + assertEquals(2, records.size()); + assertEquals("1.2.3.4", records.get(0).getContent()); + } + + @Test + void testRecordList_WithPaging() throws Exception { + ZoneEntity zone = createTestZone(); + PagingRequest pagingRequest = PagingRequest.of(10, 1); + + RecordMultipleResponse mockResponse = new RecordMultipleResponse(); + mockResponse.setResultInfo(createResultInfo(1)); + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + mockResponse.setResult(List.of(rec1)); + mockResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).getRequest(anyString(), eq(RecordMultipleResponse.class)); + + List records = spyClient.recordList(zone, pagingRequest); + + assertNotNull(records); + assertEquals(1, records.size()); + } + + @Test + void testRecordList_BySld() throws Exception { + ZoneEntity zone = createTestZone(); + + RecordMultipleResponse mockResponse = new RecordMultipleResponse(); + mockResponse.setResultInfo(createResultInfo(1)); + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + mockResponse.setResult(List.of(rec1)); + mockResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).getRequest(anyString(), eq(RecordMultipleResponse.class)); + + List records = spyClient.recordList(zone, "test"); + + assertNotNull(records); + assertEquals(1, records.size()); + assertEquals(TEST_ZONE_ID, records.get(0).getZoneId()); + } + + @Test + void testRecordList_BySldAndType() throws Exception { + ZoneEntity zone = createTestZone(); + + RecordMultipleResponse mockResponse = new RecordMultipleResponse(); + mockResponse.setResultInfo(createResultInfo(1)); + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + RecordEntity rec2 = RecordEntity.build("test.example.com", RecordType.AAAA, 300, "::1"); + mockResponse.setResult(List.of(rec1, rec2)); + mockResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).getRequest(anyString(), eq(RecordMultipleResponse.class)); + + List records = spyClient.recordList(zone, "test", RecordType.A); + + assertNotNull(records); + assertEquals(1, records.size()); + assertEquals(RecordType.A, RecordType.valueOf(records.get(0).getType())); + } + + @Test + void testRecordList_ByType() throws Exception { + ZoneEntity zone = createTestZone(); + + RecordMultipleResponse mockResponse = new RecordMultipleResponse(); + mockResponse.setResultInfo(createResultInfo(1)); + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + RecordEntity rec2 = RecordEntity.build("www.example.com", RecordType.AAAA, 300, "::1"); + mockResponse.setResult(List.of(rec1, rec2)); + mockResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).getRequest(anyString(), eq(RecordMultipleResponse.class)); + + List records = spyClient.recordList(zone, RecordType.A); + + assertNotNull(records); + assertEquals(1, records.size()); + assertEquals(RecordType.A, RecordType.valueOf(records.get(0).getType())); + } + + @Test + void testRecordCreateSld() throws Exception { + ZoneEntity zone = createTestZone(); + + RecordSingleResponse mockResponse = new RecordSingleResponse(); + RecordEntity createdRecord = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + createdRecord.setId(TEST_RECORD_ID); + mockResponse.setResult(createdRecord); + mockResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).postRequest(anyString(), any(), eq(RecordSingleResponse.class)); + + RecordEntity result = spyClient.recordCreate(zone, "test.example.com", 300, RecordType.A, "1.2.3.4"); + + assertNotNull(result); + assertEquals(TEST_RECORD_ID, result.getId()); + assertEquals(TEST_ZONE_ID, result.getZoneId()); + assertEquals("1.2.3.4", result.getContent()); + } + + @Test + void testRecordDelete() throws Exception { + ZoneEntity zone = createTestZone(); + + RecordSingleResponse mockResponse = new RecordSingleResponse(); + RecordEntity deletedRecord = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + deletedRecord.setId(TEST_RECORD_ID); + mockResponse.setResult(deletedRecord); + mockResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).deleteRequest(anyString(), eq(RecordSingleResponse.class)); + + boolean result = spyClient.recordDelete(zone, TEST_RECORD_ID); + + assertTrue(result); + } + + @Test + void testRecordUpdate() throws Exception { + ZoneEntity zone = createTestZone(); + RecordEntity record = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + record.setId(TEST_RECORD_ID); + + RecordSingleResponse mockResponse = new RecordSingleResponse(); + RecordEntity updatedRecord = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.5"); + updatedRecord.setId(TEST_RECORD_ID); + mockResponse.setResult(updatedRecord); + mockResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).patchRequest(anyString(), any(), eq(RecordSingleResponse.class)); + + RecordEntity result = spyClient.recordUpdate(zone, record); + + assertNotNull(result); + assertEquals(TEST_RECORD_ID, result.getId()); + } + + @Test + void testRecordDeleteTypeIfExists() throws Exception { + ZoneEntity zone = createTestZone(); + + RecordMultipleResponse listResponse = new RecordMultipleResponse(); + listResponse.setResultInfo(createResultInfo(1)); + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + rec1.setId(TEST_RECORD_ID); + listResponse.setResult(List.of(rec1)); + listResponse.setResponseResultInfo(createSuccessResultInfo()); + + RecordSingleResponse deleteResponse = new RecordSingleResponse(); + RecordEntity deletedRecord = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + deletedRecord.setId(TEST_RECORD_ID); + deleteResponse.setResult(deletedRecord); + deleteResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(listResponse).when(spyClient).getRequest(anyString(), eq(RecordMultipleResponse.class)); + doReturn(deleteResponse).when(spyClient).deleteRequest(anyString(), eq(RecordSingleResponse.class)); + + // Should not throw exception + spyClient.recordDeleteTypeIfExists(zone, "test", RecordType.A); + + verify(spyClient, times(1)).getRequest(anyString(), eq(RecordMultipleResponse.class)); + verify(spyClient, times(1)).deleteRequest(anyString(), eq(RecordSingleResponse.class)); + } + + @Test + void testRecordDeleteTypeIfExists_NotFound() throws Exception { + ZoneEntity zone = createTestZone(); + + CfDnsClient spyClient = spy(client); + doThrow(new CloudflareNotFoundException("Not found")).when(spyClient).recordList(any(), anyString(), any()); + + // Should not throw exception when record doesn't exist + assertDoesNotThrow(() -> spyClient.recordDeleteTypeIfExists(zone, "test", RecordType.A)); + } + + @Test + void testRecordBatch() throws Exception { + ZoneEntity zone = createTestZone(); + + RecordEntity postRecord = RecordEntity.build("new.example.com", RecordType.A, 300, "1.2.3.4"); + RecordEntity patchRecord = RecordEntity.build(TEST_RECORD_ID, "1.2.3.5"); + RecordEntity deleteRecord = RecordEntity.build("old.example.com", RecordType.A, 300, "1.2.3.6"); + deleteRecord.setId("rec999"); + + BatchResponse mockResponse = createBatchResponse(); + BatchEntry resultEntry = new BatchEntry(); + resultEntry.setPosts(List.of(postRecord)); + resultEntry.setPatches(List.of(patchRecord)); + mockResponse.setResult(resultEntry); + mockResponse.setResponseResultInfo(createSuccessResultInfo()); + + CfDnsClient spyClient = spy(client); + doReturn(mockResponse).when(spyClient).postRequest(anyString(), any(), eq(BatchResponse.class)); + + BatchEntry result = spyClient.recordBatch(zone, + List.of(postRecord), + null, + List.of(patchRecord), + List.of(deleteRecord)); + + assertNotNull(result); + assertNotNull(result.getPosts()); + assertEquals(1, result.getPosts().size()); + assertEquals(TEST_ZONE_ID, result.getPosts().get(0).getZoneId()); + } +} diff --git a/src/test/java/codes/thischwa/cf/CfRequestTest.java b/src/test/java/codes/thischwa/cf/CfRequestTest.java index 1990c2c..14aa061 100644 --- a/src/test/java/codes/thischwa/cf/CfRequestTest.java +++ b/src/test/java/codes/thischwa/cf/CfRequestTest.java @@ -27,7 +27,7 @@ public class CfRequestTest { @Test public void testBuildRecordInfoName() { - String result = CfRequest.RECORD_INFO_NAME.buildPath("zone123", "sub.domain.com"); + String result = CfRequest.RECORD_LIST_NAME.buildPath("zone123", "sub.domain.com"); assertEquals("/zones/zone123/dns_records?name=sub.domain.com", result); } @@ -45,7 +45,7 @@ public class CfRequestTest { @Test public void testBuildRecordInfo() { - String result = CfRequest.RECORD_INFO_NAME.buildPath("zone123", "sld.domain.com"); + String result = CfRequest.RECORD_LIST_NAME.buildPath("zone123", "sld.domain.com"); assertEquals("/zones/zone123/dns_records?name=sld.domain.com", result); } diff --git a/src/test/java/codes/thischwa/cf/fluent/FluentApiTest.java b/src/test/java/codes/thischwa/cf/fluent/FluentApiTest.java new file mode 100644 index 0000000..f87c330 --- /dev/null +++ b/src/test/java/codes/thischwa/cf/fluent/FluentApiTest.java @@ -0,0 +1,248 @@ +package codes.thischwa.cf.fluent; + +import codes.thischwa.cf.CfDnsClient; +import codes.thischwa.cf.CloudflareApiException; +import codes.thischwa.cf.model.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for the Fluent API package (ZoneOperations and RecordOperations). + */ +class FluentApiTest { + + private CfDnsClient mockClient; + private ZoneEntity testZone; + + private static final String TEST_ZONE_ID = "zone123"; + private static final String TEST_ZONE_NAME = "example.com"; + private static final String TEST_SLD = "test"; + private static final String TEST_RECORD_ID = "rec123"; + + @BeforeEach + void setUp() { + mockClient = mock(CfDnsClient.class); + testZone = new ZoneEntity(); + testZone.setId(TEST_ZONE_ID); + testZone.setName(TEST_ZONE_NAME); + } + + @Test + void testZoneOperations_GetRecord() throws CloudflareApiException { + ZoneOperations zoneOps = new ZoneOperationsImpl(mockClient, testZone); + + RecordOperations recordOps = zoneOps.getRecord(TEST_SLD); + + assertNotNull(recordOps); + assertInstanceOf(RecordOperationsImpl.class, recordOps); + } + + @Test + void testZoneOperations_GetRecordWithTypes() throws CloudflareApiException { + ZoneOperations zoneOps = new ZoneOperationsImpl(mockClient, testZone); + + RecordOperations recordOps = zoneOps.getRecord(TEST_SLD, RecordType.A, RecordType.AAAA); + + assertNotNull(recordOps); + assertInstanceOf(RecordOperationsImpl.class, recordOps); + } + + @Test + void testZoneOperations_List() throws CloudflareApiException { + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + RecordEntity rec2 = RecordEntity.build("www.example.com", RecordType.A, 300, "1.2.3.5"); + List expectedRecords = List.of(rec1, rec2); + + when(mockClient.recordList(eq(testZone), any(RecordType[].class))) + .thenReturn(expectedRecords); + + ZoneOperations zoneOps = new ZoneOperationsImpl(mockClient, testZone); + List result = zoneOps.list(); + + assertNotNull(result); + assertEquals(2, result.size()); + verify(mockClient, times(1)).recordList(eq(testZone), any(RecordType[].class)); + } + + @Test + void testZoneOperations_ListWithTypes() throws CloudflareApiException { + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + List expectedRecords = List.of(rec1); + + when(mockClient.recordList(eq(testZone), any(RecordType[].class))) + .thenReturn(expectedRecords); + + ZoneOperations zoneOps = new ZoneOperationsImpl(mockClient, testZone); + List result = zoneOps.list(RecordType.A); + + assertNotNull(result); + assertEquals(1, result.size()); + verify(mockClient, times(1)).recordList(eq(testZone), any(RecordType[].class)); + } + + @Test + void testRecordOperations_Get() throws CloudflareApiException { + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + List expectedRecords = List.of(rec1); + + when(mockClient.recordList(eq(testZone), eq(TEST_SLD), any())) + .thenReturn(expectedRecords); + + RecordOperations recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, null); + List result = recordOps.get(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("1.2.3.4", result.get(0).getContent()); + verify(mockClient, times(1)).recordList(eq(testZone), eq(TEST_SLD), any()); + } + + @Test + void testRecordOperations_GetWithTypes() throws CloudflareApiException { + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + List expectedRecords = List.of(rec1); + + RecordType[] types = {RecordType.A}; + when(mockClient.recordList(eq(testZone), eq(TEST_SLD), eq(types))) + .thenReturn(expectedRecords); + + RecordOperations recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, types); + List result = recordOps.get(); + + assertNotNull(result); + assertEquals(1, result.size()); + verify(mockClient, times(1)).recordList(eq(testZone), eq(TEST_SLD), eq(types)); + } + + @Test + void testRecordOperations_Create() throws CloudflareApiException { + RecordEntity createdRecord = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + createdRecord.setId(TEST_RECORD_ID); + + when(mockClient.recordCreateSld(eq(testZone), eq(TEST_SLD), eq(300), eq(RecordType.A), eq("1.2.3.4"))) + .thenReturn(createdRecord); + + RecordOperations recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, null); + RecordEntity result = recordOps.create(RecordType.A, "1.2.3.4", 300); + + assertNotNull(result); + assertEquals(TEST_RECORD_ID, result.getId()); + assertEquals("1.2.3.4", result.getContent()); + verify(mockClient, times(1)).recordCreateSld(eq(testZone), eq(TEST_SLD), eq(300), eq(RecordType.A), eq("1.2.3.4")); + } + + @Test + void testRecordOperations_Update_Success() throws CloudflareApiException { + RecordEntity existingRecord = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + existingRecord.setId(TEST_RECORD_ID); + + RecordEntity updatedRecord = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.5"); + updatedRecord.setId(TEST_RECORD_ID); + + when(mockClient.recordList(eq(testZone), eq(TEST_SLD), any())) + .thenReturn(List.of(existingRecord)); + when(mockClient.recordUpdate(eq(testZone), any(RecordEntity.class))) + .thenReturn(updatedRecord); + + RecordOperations recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, null); + RecordEntity result = recordOps.update("1.2.3.5"); + + assertNotNull(result); + assertEquals("1.2.3.5", result.getContent()); + verify(mockClient, times(1)).recordList(eq(testZone), eq(TEST_SLD), any()); + verify(mockClient, times(1)).recordUpdate(eq(testZone), any(RecordEntity.class)); + } + + @Test + void testRecordOperations_Update_NoRecordsFound() throws CloudflareApiException { + when(mockClient.recordList(eq(testZone), eq(TEST_SLD), any())) + .thenReturn(List.of()); + + RecordOperations recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, null); + + CloudflareApiException exception = assertThrows(CloudflareApiException.class, () -> { + recordOps.update("1.2.3.5"); + }); + + assertTrue(exception.getMessage().contains("No recs found")); + verify(mockClient, times(1)).recordList(eq(testZone), eq(TEST_SLD), any()); + verify(mockClient, never()).recordUpdate(any(), any()); + } + + @Test + void testRecordOperations_Update_MultipleRecordsFound() throws CloudflareApiException { + RecordEntity rec1 = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + RecordEntity rec2 = RecordEntity.build("test.example.com", RecordType.AAAA, 300, "::1"); + + when(mockClient.recordList(eq(testZone), eq(TEST_SLD), any())) + .thenReturn(List.of(rec1, rec2)); + + RecordOperations recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, null); + + CloudflareApiException exception = assertThrows(CloudflareApiException.class, () -> { + recordOps.update("1.2.3.5"); + }); + + assertTrue(exception.getMessage().contains("Multiple recs found")); + verify(mockClient, times(1)).recordList(eq(testZone), eq(TEST_SLD), any()); + verify(mockClient, never()).recordUpdate(any(), any()); + } + + @Test + void testRecordOperations_Delete() throws CloudflareApiException { + doNothing().when(mockClient).recordDeleteTypeIfExists(eq(testZone), eq(TEST_SLD), any(RecordType[].class)); + + RecordOperations recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, null); + recordOps.delete(RecordType.A, RecordType.AAAA); + + verify(mockClient, times(1)).recordDeleteTypeIfExists(eq(testZone), eq(TEST_SLD), any(RecordType[].class)); + } + + @Test + void testRecordOperations_DeleteSingleType() throws CloudflareApiException { + doNothing().when(mockClient).recordDeleteTypeIfExists(eq(testZone), eq(TEST_SLD), any(RecordType[].class)); + + RecordOperations recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, null); + recordOps.delete(RecordType.A); + + verify(mockClient, times(1)).recordDeleteTypeIfExists(eq(testZone), eq(TEST_SLD), any(RecordType[].class)); + } + + @Test + void testFluentApiChaining() throws CloudflareApiException { + // Test that fluent API chaining works correctly + RecordEntity createdRecord = RecordEntity.build("test.example.com", RecordType.A, 300, "1.2.3.4"); + createdRecord.setId(TEST_RECORD_ID); + + when(mockClient.recordCreateSld(eq(testZone), eq(TEST_SLD), eq(300), eq(RecordType.A), eq("1.2.3.4"))) + .thenReturn(createdRecord); + + ZoneOperations zoneOps = new ZoneOperationsImpl(mockClient, testZone); + RecordEntity result = zoneOps.getRecord(TEST_SLD).create(RecordType.A, "1.2.3.4", 300); + + assertNotNull(result); + assertEquals(TEST_RECORD_ID, result.getId()); + } + + @Test + void testConstructorFields() { + // Test that constructor properly initializes fields + RecordType[] types = {RecordType.A, RecordType.AAAA}; + RecordOperationsImpl recordOps = new RecordOperationsImpl(mockClient, testZone, TEST_SLD, types); + + assertNotNull(recordOps); + } + + @Test + void testZoneOperationsImplConstructor() { + ZoneOperationsImpl zoneOps = new ZoneOperationsImpl(mockClient, testZone); + + assertNotNull(zoneOps); + } +} diff --git a/src/test/java/codes/thischwa/cf/model/BatchEntryTest.java b/src/test/java/codes/thischwa/cf/model/BatchEntryTest.java new file mode 100644 index 0000000..e46bb32 --- /dev/null +++ b/src/test/java/codes/thischwa/cf/model/BatchEntryTest.java @@ -0,0 +1,47 @@ +package codes.thischwa.cf.model; + +import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +public class BatchEntryTest { + + @Test + void testBatchEntry() { + BatchEntry entry = new BatchEntry(); + assertEquals("", entry.getId()); + + List patches = new ArrayList<>(); + List posts = new ArrayList<>(); + List puts = new ArrayList<>(); + List deletes = new ArrayList<>(); + + entry.setPatches(patches); + entry.setPosts(posts); + entry.setPuts(puts); + entry.setDeletes(deletes); + + assertSame(patches, entry.getPatches()); + assertSame(posts, entry.getPosts()); + assertSame(puts, entry.getPuts()); + assertSame(deletes, entry.getDeletes()); + } + + @Test + void testLombokMethods() { + BatchEntry entry1 = new BatchEntry(); + BatchEntry entry2 = new BatchEntry(); + assertEquals(entry1, entry2); + assertEquals(entry1.hashCode(), entry2.hashCode()); + + List patches = List.of(new RecordEntity()); + entry1.setPatches(patches); + assertNotEquals(entry1, entry2); + + entry2.setPatches(patches); + assertEquals(entry1, entry2); + + assertTrue(entry1.toString().contains("patches=")); + } +} diff --git a/src/test/java/codes/thischwa/cf/model/PagingRequestTest.java b/src/test/java/codes/thischwa/cf/model/PagingRequestTest.java index 8c6512f..f399942 100644 --- a/src/test/java/codes/thischwa/cf/model/PagingRequestTest.java +++ b/src/test/java/codes/thischwa/cf/model/PagingRequestTest.java @@ -7,15 +7,37 @@ import static org.junit.jupiter.api.Assertions.*; public class PagingRequestTest { @Test - public void testBuildPath() { + void testBuildPath() { String result = PagingRequest.defaultPaging().addQueryString("/zones"); - assertEquals("/zones?page=1&perPage=5000000", result); + assertEquals("/zones?page=1&per_page=1000", result); } @Test - public void testBuildPathAdditional() { + void testBuildPathAdditional() { String result = new PagingRequest( 10, 100).addQueryString("/zones?foo=bar"); - assertEquals("/zones?foo=bar&page=10&perPage=100", result); + assertEquals("/zones?foo=bar&page=10&per_page=100", result); + } + + @Test + void testGetPagingParams() { + PagingRequest request = PagingRequest.of(2, 50); + java.util.Map params = request.getPagingParams(); + assertEquals("2", params.get("page")); + assertEquals("50", params.get("perPage")); + } + + @Test + void testLombokMethods() { + PagingRequest req1 = PagingRequest.of(1, 10); + PagingRequest req2 = PagingRequest.of(1, 10); + assertEquals(req1, req2); + assertEquals(req1.hashCode(), req2.hashCode()); + assertTrue(req1.toString().contains("page=1")); + + req1.setPage(5); + assertEquals(5, req1.getPage()); + req1.setPerPage(20); + assertEquals(20, req1.getPerPage()); } } diff --git a/src/test/java/codes/thischwa/cf/model/RecordEntityTest.java b/src/test/java/codes/thischwa/cf/model/RecordEntityTest.java new file mode 100644 index 0000000..4509b7d --- /dev/null +++ b/src/test/java/codes/thischwa/cf/model/RecordEntityTest.java @@ -0,0 +1,70 @@ +package codes.thischwa.cf.model; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class RecordEntityTest { + + @Test + void testBuildWithAttributes() { + RecordEntity rec = RecordEntity.build("example.com", RecordType.A, 120, "1.2.3.4"); + assertEquals("example.com", rec.getName()); + assertEquals("A", rec.getType()); + assertEquals(120, rec.getTtl()); + assertEquals("1.2.3.4", rec.getContent()); + } + + @Test + void testBuildWithIdAndContent() { + RecordEntity rec = RecordEntity.build("id-123", "1.2.3.4"); + assertEquals("id-123", rec.getId()); + assertEquals("1.2.3.4", rec.getContent()); + } + + @Test + void testBuildWithIdAndAttributes() { + RecordEntity rec = RecordEntity.build("id-123", "example.com", "A", 120, "1.2.3.4"); + assertEquals("id-123", rec.getId()); + assertEquals("example.com", rec.getName()); + assertEquals("A", rec.getType()); + assertEquals(120, rec.getTtl()); + assertEquals("1.2.3.4", rec.getContent()); + } + + @Test + void testBuildWithInvalidType() { + assertThrows(IllegalArgumentException.class, () -> + RecordEntity.build("id-123", "example.com", "INVALID", 120, "1.2.3.4") + ); + } + + @Test + void testGetSld() { + RecordEntity rec = new RecordEntity(); + assertNull(rec.getSld()); + + rec.setName("sub.example.com"); + assertEquals("sub", rec.getSld()); + + rec.setName("example.com"); + assertEquals("example", rec.getSld()); + + rec.setName("host"); + assertEquals("host", rec.getSld()); + + rec.setName(".dotstart"); + assertEquals(".dotstart", rec.getSld()); + } + + @Test + void testGetSldWithZoneName() { + RecordEntity rec = new RecordEntity(); + rec.setName("sub.example.com"); + rec.setZoneName("example.com"); + assertEquals("sub", rec.getSld()); + + rec.setName("my.sub.example.com"); + rec.setZoneName("example.com"); + assertEquals("my.sub", rec.getSld()); + } +} diff --git a/src/test/java/codes/thischwa/cf/model/RecordTypeTest.java b/src/test/java/codes/thischwa/cf/model/RecordTypeTest.java new file mode 100644 index 0000000..9292aae --- /dev/null +++ b/src/test/java/codes/thischwa/cf/model/RecordTypeTest.java @@ -0,0 +1,40 @@ +package codes.thischwa.cf.model; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class RecordTypeTest { + + @Test + void testGetType() { + assertEquals("A", RecordType.A.getType()); + assertEquals("AAAA", RecordType.AAAA.getType()); + assertEquals("CNAME", RecordType.CNAME.getType()); + assertEquals("TXT", RecordType.TXT.getType()); + assertEquals("SRV", RecordType.SRV.getType()); + assertEquals("LOC", RecordType.LOC.getType()); + assertEquals("MX", RecordType.MX.getType()); + assertEquals("NS", RecordType.NS.getType()); + assertEquals("CAA", RecordType.CAA.getType()); + assertEquals("CERT", RecordType.CERT.getType()); + assertEquals("DNSKEY", RecordType.DNSKEY.getType()); + assertEquals("DS", RecordType.DS.getType()); + assertEquals("NAPTR", RecordType.NAPTR.getType()); + assertEquals("SMIMEA", RecordType.SMIMEA.getType()); + assertEquals("SSHFP", RecordType.SSHFP.getType()); + assertEquals("TLSA", RecordType.TLSA.getType()); + assertEquals("URI", RecordType.URI.getType()); + } + + @Test + void testToString() { + assertEquals("A", RecordType.A.toString()); + assertEquals("CNAME", RecordType.CNAME.toString()); + } + + @Test + void testValueOf() { + assertEquals(RecordType.A, RecordType.valueOf("A")); + assertEquals(RecordType.CNAME, RecordType.valueOf("CNAME")); + } +} diff --git a/src/test/java/codes/thischwa/cf/model/ZoneEntityTest.java b/src/test/java/codes/thischwa/cf/model/ZoneEntityTest.java new file mode 100644 index 0000000..08d1abc --- /dev/null +++ b/src/test/java/codes/thischwa/cf/model/ZoneEntityTest.java @@ -0,0 +1,41 @@ +package codes.thischwa.cf.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.time.LocalDateTime; +import java.util.Set; +import org.junit.jupiter.api.Test; + +public class ZoneEntityTest { + + @Test + void testZoneEntity() { + ZoneEntity zone = new ZoneEntity(); + zone.setId("zone-id"); + zone.setName("example.com"); + zone.setDevelopmentMode(7200); + Set ns = Set.of("ns1.cloudflare.com", "ns2.cloudflare.com"); + zone.setNameServers(ns); + zone.setOriginalNameServers(ns); + LocalDateTime now = LocalDateTime.now(); + zone.setCreatedOn(now); + zone.setModifiedOn(now); + zone.setActivatedOn(now); + zone.setStatus("active"); + zone.setPaused(false); + zone.setType("full"); + + assertEquals("zone-id", zone.getId()); + assertEquals("example.com", zone.getName()); + assertEquals(7200, zone.getDevelopmentMode()); + assertEquals(ns, zone.getNameServers()); + assertEquals(ns, zone.getOriginalNameServers()); + assertEquals(now, zone.getCreatedOn()); + assertEquals(now, zone.getModifiedOn()); + assertEquals(now, zone.getActivatedOn()); + assertEquals("active", zone.getStatus()); + assertFalse(zone.getPaused()); + assertEquals("full", zone.getType()); + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 42dc8c6..f272639 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -1,5 +1,5 @@ - +