diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 879ffd2..ba1aaa2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,19 +6,31 @@ on: tags: ['*'] pull_request: concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: + format: + name: Code formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: '1' + - uses: julia-actions/cache@v1 + - name: Install Runic + run: julia -e 'using Pkg; Pkg.add("Runic")' + - name: Check formatting + run: julia -e 'using Runic; Runic.main(["--check", "src/", "test/", "docs/"])' + test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + name: Test (${{ matrix.version }}; ${{ matrix.os }} ${{ matrix.arch }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: version: - - '1.9' + - '1.10' - 'nightly' os: - ubuntu-latest @@ -33,8 +45,11 @@ jobs: - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 + timeout-minutes: 10 - uses: julia-actions/julia-processcoverage@v1 + if: matrix.version == '1.10' && matrix.os == 'ubuntu-latest' - uses: codecov/codecov-action@v3 + if: matrix.version == '1.10' && matrix.os == 'ubuntu-latest' with: files: lcov.info docs: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d2915bc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,291 @@ +# AGENTS.md - Development Guide for KEGGAPI.jl + +## Project Overview + +KEGGAPI.jl is a Julia package providing programmatic access to the KEGG (Kyoto Encyclopedia of Genes and Genomes) REST API. This package offers functions to retrieve biological data including pathways, compounds, genes, and genomic information. + +## Build, Test & Development Commands + +### Basic Commands +```bash +# Install package dependencies +julia --project -e "using Pkg; Pkg.instantiate()" + +# Run all tests +julia --project -e "using Pkg; Pkg.test()" + +# Build package +julia --project -e "using Pkg; Pkg.build()" + +# Run single test file +julia --project test/runtests.jl + +# Start Julia REPL with project +julia --project + +# Generate documentation +julia --project=docs docs/make.jl +``` + +### Development Setup +```bash +# Activate development environment +julia --project=dev -e "using Pkg; Pkg.develop(PackageSpec(path=pwd()))" + +# Add development dependencies +julia --project -e "using Pkg; Pkg.add([\"Test\", \"Runic\", \"Aqua\"])" + +# Format code with Runic.jl +julia --project -e "using Runic; Runic.format(\".\")" +``` + +### Testing Strategy + +**Unit Tests (Mocked):** Test wrapper functions without API calls +- `kegg_get()`, `list()`, `info()`, `get_image()`, `conv()`, `find()`, `link()` + +**Integration Tests (Real API):** Test core request functionality +- `KEGGAPI.request()` - Must test with actual KEGG API endpoints + +**Test Organization:** +- All tests in `test/runtests.jl` +- Use `@testset` for logical groupings +- Test both success and failure scenarios + +## Code Style Guidelines + +### Formatting +- **Formatter:** Runic.jl (enforced in CI) +- **Line Length:** No strict limit, but prefer readability +- **Indentation:** 4 spaces (no tabs) + +### Imports & Dependencies +```julia +# Qualified imports preferred +import HTTP: get + +# Standard library imports +using Test + +# Export public API clearly +export request, info, list, find, get_image, kegg_get, conv, link, save_image +``` + +### Function & Variable Naming +```julia +# Functions: snake_case +function kegg_get(query::Vector{String}) +function request_other(url::String) + +# Types: PascalCase +struct RequestError <: Exception +struct KeggTupleList + +# Constants: UPPER_SNAKE_CASE +const DEFAULT_CHUNK_SIZE = 10 + +# Variables: snake_case +chunk_size = 10 +response_text = request(url) +``` + +### Type Annotations +```julia +# Always annotate function parameters +function request(url::String) +function kegg_get(query::Vector{String}, option::String = "") + +# Use concrete types in structs +struct RequestError <: Exception + message::String +end + +# Return type annotations for public API +function info(database::String)::String +``` + +### Documentation +```julia +""" +KEGGAPI.function_name(param1, param2) -> ReturnType + +Brief description of what the function does. + +Longer description if needed, explaining parameters, +behavior, and any important details. + +# Examples +```julia-repl +julia> KEGGAPI.function_name("example") +"result" +``` + +# Arguments +- `param1::String`: Description of parameter +- `param2::Int`: Description with default value + +# Returns +- `String`: Description of return value + +# Throws +- `RequestError`: When API request fails +""" +``` + +### Error Handling +```julia +# Custom exception types +struct RequestError <: Exception + message::String +end + +# Explicit error throwing +if response.status != 200 + throw(RequestError("Request failed with status $(response.status)")) +end + +# Error testing in tests +@test_throws RequestError KEGGAPI.info("invalid_db") +``` + +### Control Flow & Logic +```julia +# Clear conditional structure +if condition + action() +elseif other_condition + other_action() +else + default_action() +end + +# Prefer early returns to reduce nesting +function process_data(data) + if isempty(data) + return nothing + end + + # Main logic here + return processed_data +end +``` + +### Comments +```julia +# Use comments to explain WHY, not WHAT +chunk_size = 10 # KEGG API optimal batch size + +# Complex logic deserves explanation +# Split queries into chunks to respect API rate limits +for i in 1:query_chunks + # Process each chunk... +end + +# TODO comments for future improvements +# TODO: Add retry logic for failed requests +``` + +### File Organization +```julia +# Main module (src/KEGGAPI.jl) +module KEGGAPI +import HTTP: get +export functions... +include("Structures.jl") # Types first +include("Requests.jl") # Core functionality +include("specialized_functions.jl") +end + +# Each file should have focused responsibility +# Structures.jl - Type definitions +# Requests.jl - HTTP request handling +# Info.jl - Info-related functions +``` + +### Testing Patterns +```julia +@testset "Function Group" begin + @testset "specific_function" begin + # Test successful case + result = KEGGAPI.specific_function("valid_input") + @test isa(result, ExpectedType) + @test length(result) > 0 + + # Test error case + @test_throws RequestError KEGGAPI.specific_function("invalid") + end +end + +# Integration test patterns (for KEGGAPI.request only) +@testset "Integration Tests" begin + @testset "real API calls" begin + # Test with known working endpoint + result = KEGGAPI.request("https://rest.kegg.jp/info/kegg") + @test isa(result, String) + @test !isempty(result) + end +end +``` + +## CI/CD Pipeline + +### Current Status +- **Testing:** Julia 1.9 + nightly on Ubuntu +- **Documentation:** Auto-builds and deploys +- **Dependencies:** CompatHelper for automated updates +- **Releases:** TagBot for automated tagging + +### Planned Enhancements +- **Formatting:** Runic.jl enforcement (blocks PRs) +- **Extended Testing:** Julia 1.10, nightly +- **Code Quality:** Aqua.jl integration +- **Real API Tests:** For `KEGGAPI.request()` function +- **Semantic Versioning:** Automated enforcement +- **Release Notes:** Automated generation + +### Development Workflow +1. Format code with Runic.jl before committing +2. Ensure all tests pass locally +3. Write tests for new functionality +4. Update documentation for public API changes +5. Breaking changes are acceptable with proper notification + +## Common Patterns & Examples + +### Making API Requests +```julia +# Always use the request() function for HTTP calls +response_text = request("https://rest.kegg.jp/info/$database") + +# Handle errors appropriately +try + data = request(url) + return parse_response(data) +catch e + if e isa RequestError + # Handle API errors + return default_value + else + rethrow(e) + end +end +``` + +### Processing API Responses +```julia +# Split responses consistently +for datum in split(response_text, "\n///\n") + push!(data, datum) +end + +# Clean up response text +response_text2 = replace(response_text, r"\n///([^/]*)$" => "") +``` + +### Rate Limiting +```julia +# Always include delays between API calls +sleep(0.1) # 100ms delay between requests +``` + +This guide ensures consistent, maintainable code that follows Julia best practices while respecting the KEGG API's requirements and limitations. \ No newline at end of file diff --git a/Project.toml b/Project.toml index 04e5ad7..1b4c0f5 100644 --- a/Project.toml +++ b/Project.toml @@ -1,17 +1,18 @@ name = "KEGGAPI" uuid = "e8256861-d17c-4800-bf17-838497555b93" -authors = ["Nicholas Geoffrion", "Maria Victoria Aguilar Pontes"] version = "1.0.0-DEV" +authors = ["Nicholas Geoffrion", "Maria Victoria Aguilar Pontes", "Carlos Vigil-Vásquez"] [deps] HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" [compat] HTTP = "1" -julia = "1" +julia = "1.10" [extras] +Runic = "62bfec6d-59d7-401d-8490-b29ee721c001" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Test", "Runic"] diff --git a/docs/make.jl b/docs/make.jl index 25f44ca..1c0e548 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,20 +1,20 @@ using KEGGAPI using Documenter -DocMeta.setdocmeta!(KEGGAPI, :DocTestSetup, :(using KEGGAPI); recursive=true) +DocMeta.setdocmeta!(KEGGAPI, :DocTestSetup, :(using KEGGAPI); recursive = true) makedocs(; - modules=[KEGGAPI], - authors="Nicholas Geoffrion, Maria Victoria Aguilar Pontes", - repo="https://github.com/bwbioinfo/KEGGAPI.jl/blob/{commit}{path}#{line}", - sitename="KEGGAPI.jl", - format=Documenter.HTML(; - prettyurls=get(ENV, "CI", "false") == "true", - canonical="https://bwbioinfo.github.io/KEGGAPI.jl", - edit_link="main", - assets=String[], + modules = [KEGGAPI], + authors = "Nicholas Geoffrion, Maria Victoria Aguilar Pontes, Carlos Vigil-Vásquez", + repo = "https://github.com/cvigilv/KEGGAPI.jl/blob/{commit}{path}#{line}", + sitename = "KEGGAPI.jl", + format = Documenter.HTML(; + prettyurls = get(ENV, "CI", "false") == "true", + canonical = "https://cvigilv.github.io/KEGGAPI.jl", + edit_link = "main", + assets = String[], ), - pages=[ + pages = [ "Home" => "index.md", "Manual" => Any[ "Guide" => "man/guide.md", @@ -26,6 +26,6 @@ makedocs(; ) deploydocs(; - repo="github.com/bwbioinfo/KEGGAPI.jl", - devbranch="main", + repo = "github.com/cvigilv/KEGGAPI.jl", + devbranch = "main", ) diff --git a/examples/example01.jl b/examples/example01.jl index 8c94cdf..2fc6bd8 100644 --- a/examples/example01.jl +++ b/examples/example01.jl @@ -73,4 +73,4 @@ kegg_link = KEGGAPI.link("pathway", "dr"); ## Get an image kegg_image = KEGGAPI.get_image("hsa00010"); ## Save an image -KEGGAPI.save_image(kegg_image, "image.png"); \ No newline at end of file +KEGGAPI.save_image(kegg_image, "image.png"); diff --git a/src/Conv.jl b/src/Conv.jl index 5401cfe..8f9b673 100644 --- a/src/Conv.jl +++ b/src/Conv.jl @@ -1,4 +1,3 @@ - """ KEGGAPI.conv(, ) @@ -19,12 +18,11 @@ function conv(target_db::String, source_db::String) # Define the URL for the API request. url = "https://rest.kegg.jp/conv/$target_db/$source_db" response_text = request(url) - kegg_data = + kegg_data = conv_parser( - response_text, - url - ) - # return the arrays + response_text, + url + ) + # return the arrays return kegg_data end - diff --git a/src/Find.jl b/src/Find.jl index 37751ac..97eabaf 100644 --- a/src/Find.jl +++ b/src/Find.jl @@ -1,4 +1,3 @@ - """ KEGGAPI.find(database, query) -> KeggTupleList @@ -10,10 +9,10 @@ KEGGAPI.find("compound","glucose") ``` """ function find(database::String, query::String, option::String = "") -# This function retrieves a list of entries from a specific database from the KEGG API. + # This function retrieves a list of entries from a specific database from the KEGG API. query = replace(query, " " => "+") # Check if the requested database is a "pathway" database - if database in ["pathway","genes", "module", "orthology", "disease", "brite"] + if database in ["pathway", "genes", "module", "orthology", "disease", "brite"] if option != "" throw(ArgumentError("The option argument is not available for pathway and genes databases")) end @@ -34,7 +33,7 @@ function find(database::String, query::String, option::String = "") # If the database is not a pathway database, print a message indicating that the requested list is not available print("The find route you are looking for is not available") # Return an empty array - kegg_data = [] + kegg_data = [] end # Return the parsed data or an empty array if not available return kegg_data diff --git a/src/Get.jl b/src/Get.jl index 494ffa0..27c6525 100644 --- a/src/Get.jl +++ b/src/Get.jl @@ -1,4 +1,3 @@ - """ KEGGAPI.get(query, option) -> Vector @@ -25,18 +24,18 @@ first(kegg_get_compounds) """ function kegg_get(query::Vector{String}, option::String = "") # This function retrieves a list of entries from a specific database from the KEGG API. - + # Set the chunk size for processing multiple elements in each request chunk_size = 10 - + # Calculate the number of chunks needed to process all elements in the query query_elements = length(query) query_chunks = ceil(Int, query_elements / chunk_size) - + # Initialize arrays to store URLs and retrieved data urls = String[] data = String[] - + # Check if the option is "aaseq" or "ntseq" if option == "aaseq" || option == "ntseq" # If there are more than 10 queries, process in chunks @@ -45,28 +44,28 @@ function kegg_get(query::Vector{String}, option::String = "") # Calculate start and end indices for the current chunk start_index = (i - 1) * chunk_size + 1 end_index = min(i * chunk_size, query_elements) - + # Extract the current chunk of queries chunk = query[start_index:end_index] - + # Join the queries with "+" for URL construction chunk_query = join(chunk, "+") - + # Construct the URL for the API request url = "https://rest.kegg.jp/get/$chunk_query/$option" - + # Store the URL for reference push!(urls, url) - + # Request data from the URL response_text = request(url) - + # Process the response and extract data for datum in split(response_text, r"(\n>|^>)")[2:end] datum = replace(datum, r"\n$" => "") - push!(data, ">"*datum) + push!(data, ">" * datum) end - + # Introduce a delay before the next request sleep(0.1) end @@ -78,7 +77,7 @@ function kegg_get(query::Vector{String}, option::String = "") push!(urls, url) for datum in split(response_text, r"(\n>|^>)")[2:end] datum = replace(datum, r"\n$" => "") - push!(data, ">"*datum) + push!(data, ">" * datum) end end else @@ -88,28 +87,28 @@ function kegg_get(query::Vector{String}, option::String = "") # Calculate start and end indices for the current chunk start_index = (i - 1) * chunk_size + 1 end_index = min(i * chunk_size, query_elements) - + # Extract the current chunk of queries chunk = query[start_index:end_index] - + # Join the queries with "+" for URL construction chunk_query = join(chunk, "+") - + # Construct the URL for the API request url = "https://rest.kegg.jp/get/$chunk_query/$option" - + # Store the URL for reference push!(urls, url) - + # Request data from the URL response_text = request(url) - + # Process the response and extract data response_text2 = replace(response_text, r"\n///([^/]*)$" => "") for datum in split(response_text2, "\n///\n") push!(data, datum) end - + # Introduce a delay before the next request sleep(0.1) end @@ -125,10 +124,10 @@ function kegg_get(query::Vector{String}, option::String = "") end end end - + # Combine URLs and data into a single array kegg_data = [urls, data] - + # Return the parsed data or an empty array if not available return kegg_data end diff --git a/src/Images.jl b/src/Images.jl index dc8ecb4..3079792 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -32,4 +32,4 @@ function save_image(image::Vector, filename::String) write(f, image) end return filename -end \ No newline at end of file +end diff --git a/src/Info.jl b/src/Info.jl index 35beac4..ad17809 100644 --- a/src/Info.jl +++ b/src/Info.jl @@ -17,4 +17,4 @@ function info(database::String) response_text = request(url) # Return the lines as a string. return response_text -end \ No newline at end of file +end diff --git a/src/KEGGAPI.jl b/src/KEGGAPI.jl index 32da768..6c342bd 100644 --- a/src/KEGGAPI.jl +++ b/src/KEGGAPI.jl @@ -20,4 +20,4 @@ precompile(request, (String,)) precompile(request_other, (String,)) precompile(kegg_get, (Vector,)) -end \ No newline at end of file +end diff --git a/src/Link.jl b/src/Link.jl index 235d372..e94d06b 100644 --- a/src/Link.jl +++ b/src/Link.jl @@ -1,4 +1,3 @@ - """ KEGGAPI.conv(, ) @@ -15,12 +14,11 @@ function link(target_db::String, source_db::String) # Define the URL for the API request. url = "https://rest.kegg.jp/link/$target_db/$source_db" response_text = request(url) - kegg_data = + kegg_data = conv_parser( - response_text, - url - ) - # return the arrays + response_text, + url + ) + # return the arrays return kegg_data end - diff --git a/src/List.jl b/src/List.jl index fd7aee6..36475a8 100644 --- a/src/List.jl +++ b/src/List.jl @@ -1,4 +1,3 @@ - """ KEGGAPI.list(database) @@ -17,7 +16,7 @@ function list(query::String, query_type::String = "") url = "https://rest.kegg.jp/list/$query" # Check if the requested 'list' is a "pathway" database. - if query == "organism" + if query == "organism" # Make the API request and get the response as text. response_text = request(url) # If the 'list' is "organism," call the organism_parser function to parse the response. @@ -27,7 +26,7 @@ function list(query::String, query_type::String = "") response_text = request(url) # If the 'list' is "genes," call the genes_parser function to parse the response. kegg_data = genomic_feature_parser(response_text, url) - else + else # Make the API request and get the response as text. response_text = request(url) # Return an empty array. diff --git a/src/Parsers.jl b/src/Parsers.jl index b92fdba..c894432 100644 --- a/src/Parsers.jl +++ b/src/Parsers.jl @@ -1,4 +1,3 @@ - function tuple_parser(response_text::String, url::String) # Split the response into lines lines = split(response_text, "\n") @@ -62,7 +61,6 @@ function organism_parser(response_text::String, url::String) end - function conv_parser(response_text::String, url::String) # Split the response into lines lines = split(response_text, "\n") diff --git a/src/Requests.jl b/src/Requests.jl index 3329fdb..3234b3e 100644 --- a/src/Requests.jl +++ b/src/Requests.jl @@ -1,4 +1,3 @@ - """ request(url) @@ -12,7 +11,7 @@ request("https://rest.kegg.jp/info/kegg") ``` """ function request(url::String) - response = get(url, status_exception=false, verbose=false) + response = get(url, status_exception = false, verbose = false) if (response.status == 200) return String(response.body) @@ -41,7 +40,7 @@ request_other("https://rest.kegg.jp/image/hsa00010") ``` """ function request_other(url::String) - response = get(url, status_exception=false, verbose=false) + response = get(url, status_exception = false, verbose = false) if (response.status == 200) return response.body @@ -55,4 +54,4 @@ function request_other(url::String) ) ) end -end \ No newline at end of file +end diff --git a/src/Structures.jl b/src/Structures.jl index b97ebdf..64c0bea 100644 --- a/src/Structures.jl +++ b/src/Structures.jl @@ -7,18 +7,18 @@ end mutable struct KeggTupleList url::String - colnames:: Vector{String} + colnames::Vector{String} data::Vector{Any} end mutable struct KeggOrganismList url::String - colnames:: Vector{String} + colnames::Vector{String} data::Vector{Any} end mutable struct KeggGenesList url::String - colnames:: Vector{String} + colnames::Vector{String} data::Vector{Any} -end \ No newline at end of file +end diff --git a/test/runtests.jl b/test/runtests.jl index 1ae07a0..fb94832 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,65 +1,4 @@ using KEGGAPI using Test -@testset "KEGGAPI.info" begin - # Retrieve information about a specific database from the KEGG API - kegg_info = KEGGAPI.info("kegg") - @test isa(kegg_info, String) # Check if the retrieved info is a string - @test length(kegg_info) > 0 # Check if the retrieved info is not empty - @test_throws KEGGAPI.RequestError KEGGAPI.info("fail") -end - -@testset "KEGGAPI.list" begin - # Retrieve a list of KEGG pathways - kegg_pathways = KEGGAPI.list("pathway") - @test isa(kegg_pathways, KEGGAPI.KeggTupleList) # Check if the retrieved info is a list - @test kegg_pathways != [] # Check if the retrieved list is not empty - @test_throws KEGGAPI.RequestError KEGGAPI.list("fail") -end - -@testset "KEGGAPI.get_image" begin - # Retrieve an image from the KEGG API. - kegg_image = KEGGAPI.get_image("hsa00010") - @test isa(kegg_image, Vector) # Check if the retrieve info is a vector - @test length(kegg_image) > 0 # Check if the retreived vector is not empty - @test_throws KEGGAPI.RequestError KEGGAPI.get_image("fail") -end - -@testset "KEGGAPI.save_image" begin - # Save an image to a file - file_name = "image.png" - kegg_image = KEGGAPI.get_image("hsa00010") - KEGGAPI.save_image(kegg_image, file_name) - @test isfile(file_name) # Check if the file exist - @test filesize(file_name) > 0 # Check if file is not empty -end - -@testset "KEGGAPI.conv" begin - # Convert KEGG identifiers to/from outside database - kegg_conv = KEGGAPI.conv("eco", "ncbi-geneid") - @test isa(kegg_conv, KEGGAPI.KeggTupleList) # Check if the retrieve info is a list - @test kegg_conv != [] # Check if retreived list is not empty -end - -@testset "KEGGAPI.find" begin - # Find entries in a specific database from the KEGG API - kegg_find_pathway = KEGGAPI.find("pathway", "glycolysis") - @test isa(kegg_find_pathway, KEGGAPI.KeggTupleList) # Check if the retrieve info is a list - @test kegg_find_pathway != [] # Check if retrieve list is not empty - kegg_find_compound = KEGGAPI.find("compound", "glucose") - @test isa(kegg_find_compound, KEGGAPI.KeggTupleList) # Check if the retrieve info is a list - @test kegg_find_compound != [] # Check if retrieve list is not empty - kegg_find_compound_options = KEGGAPI.find("compound", "100-150", "mol_weight") - @test isa(kegg_find_compound_options, KEGGAPI.KeggTupleList) # Check if the retrieve info is a list - @test kegg_find_compound_options != [] # Check if retrieve list is not empty - kegg_find_genes = KEGGAPI.find("genes", "glycolysis") - @test isa(kegg_find_genes, KEGGAPI.KeggTupleList) # Check if the retrieve info is a list - @test kegg_find_genes != [] # Check if retrieve list is not empty -end - -@testset "KEGGAPI.link" begin - # Find related entries by using database cross-references - kegg_link = KEGGAPI.link("pathway", "hsa") - @test isa(kegg_link, KEGGAPI.KeggTupleList) # Check if the retreive info is a list - @test kegg_link != [] # Check if retreinve list is not empty -end +include("test_requests.jl") diff --git a/test/test_requests.jl b/test/test_requests.jl new file mode 100644 index 0000000..f205b2c --- /dev/null +++ b/test/test_requests.jl @@ -0,0 +1,90 @@ +using KEGGAPI +using Test + +@testset verbose=true "Requests" begin + @testset "KEGGAPI.request" begin + # Test successful request to known working endpoint + result = KEGGAPI.request("https://rest.kegg.jp/info/kegg") + @test isa(result, String) + @test !isempty(result) + @test contains(lowercase(result), "kegg") # Basic content validation (case insensitive) + + # Test another known endpoint + result2 = KEGGAPI.request("https://rest.kegg.jp/list/pathway/hsa/01100+00230") + @test isa(result2, String) + + # Test error handling for invalid endpoint + @test_throws KEGGAPI.RequestError KEGGAPI.request("https://rest.kegg.jp/invalid/endpoint") + + # Test request_other function for binary data + image_data = KEGGAPI.request_other("https://rest.kegg.jp/get/hsa00010/image") + @test isa(image_data, Vector) + @test length(image_data) > 0 + end + + @testset "KEGGAPI.info" begin + kegg_info = KEGGAPI.info("kegg") + @test isa(kegg_info, String) + @test length(kegg_info) > 0 + @test contains(lowercase(kegg_info), "kegg") + @test_throws KEGGAPI.RequestError KEGGAPI.info("fail") + sleep(0.1) + end + + @testset "KEGGAPI.list" begin + kegg_pathways = KEGGAPI.list("pathway") + @test isa(kegg_pathways, KEGGAPI.KeggTupleList) + @test length(kegg_pathways.data) > 0 + @test_throws KEGGAPI.RequestError KEGGAPI.list("fail") + sleep(0.1) + end + + @testset "KEGGAPI.find" begin + kegg_find_pathway = KEGGAPI.find("pathway", "glycolysis") + @test isa(kegg_find_pathway, KEGGAPI.KeggTupleList) + @test length(kegg_find_pathway.data) > 0 + sleep(0.1) + + kegg_find_compound = KEGGAPI.find("compound", "glucose") + @test isa(kegg_find_compound, KEGGAPI.KeggTupleList) + @test length(kegg_find_compound.data) > 0 + sleep(0.1) + + kegg_find_genes = KEGGAPI.find("genes", "glycolysis") + @test isa(kegg_find_genes, KEGGAPI.KeggTupleList) + @test length(kegg_find_genes.data) > 0 + sleep(0.1) + end + + @testset "KEGGAPI.conv" begin + kegg_conv = KEGGAPI.conv("eco", "ncbi-geneid") + @test isa(kegg_conv, KEGGAPI.KeggTupleList) + @test length(kegg_conv.data) > 0 + sleep(0.1) + end + + @testset "KEGGAPI.link" begin + kegg_link = KEGGAPI.link("pathway", "hsa") + @test isa(kegg_link, KEGGAPI.KeggTupleList) + @test length(kegg_link.data) > 0 + sleep(0.1) + end + + @testset "KEGGAPI.get_image" begin + kegg_image = KEGGAPI.get_image("hsa00010") + @test isa(kegg_image, Vector) + @test length(kegg_image) > 0 + @test_throws KEGGAPI.RequestError KEGGAPI.get_image("fail") + sleep(0.1) + end + + @testset "KEGGAPI.save_image" begin + file_name = "test_image.png" + kegg_image = KEGGAPI.get_image("hsa00010") + KEGGAPI.save_image(kegg_image, file_name) + @test isfile(file_name) + @test filesize(file_name) > 0 + rm(file_name, force = true) + sleep(0.1) + end +end